File size: 3,260 Bytes
09c1faf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import type {
  Book,
  BookDetail,
  CreatePlanPayload,
  GoogleAuthResponse,
  Plan,
  PlanDayDetail,
  PlanSummary,
} from "./types";
import {
  getDemoBook,
  getDemoBooks,
  getDemoPlan,
  getDemoPlanDay,
  getDemoPlans,
  registerDemoPlan,
} from "./demo-data";

const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://127.0.0.1:8000";
export const DEMO_MODE = process.env.NODE_ENV === "production" && !process.env.NEXT_PUBLIC_API_BASE_URL;

async function demoRequest<T>(path: string, init?: RequestInit): Promise<T> {
  const [pathname] = path.split("?");

  if (pathname === "/auth/google") {
    return {
      user_id: "demo-user",
      email: "demo@pluckai.local",
      display_name: "Demo User",
    } as T;
  }

  if (pathname === "/books") {
    return getDemoBooks() as T;
  }

  if (pathname.startsWith("/books/")) {
    const slug = decodeURIComponent(pathname.slice("/books/".length));
    return getDemoBook(slug) as T;
  }

  if (pathname.startsWith("/users/") && pathname.endsWith("/plans")) {
    const email = decodeURIComponent(pathname.slice("/users/".length, -"/plans".length));
    return getDemoPlans(email) as T;
  }

  if (pathname === "/plans" && init?.method?.toUpperCase() === "POST") {
    const body = typeof init.body === "string" ? init.body : "{}";
    return registerDemoPlan(JSON.parse(body) as CreatePlanPayload) as T;
  }

  if (pathname.startsWith("/plans/") && pathname.includes("/days/")) {
    const segments = pathname.split("/");
    const planId = decodeURIComponent(segments[2] ?? "");
    const dayValue = Number(segments[4] ?? "1");
    return getDemoPlanDay(planId, dayValue) as T;
  }

  if (pathname.startsWith("/plans/")) {
    const planId = decodeURIComponent(pathname.slice("/plans/".length));
    return getDemoPlan(planId) as T;
  }

  throw new Error(`No demo response available for ${pathname}`);
}

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  if (DEMO_MODE) {
    return demoRequest<T>(path, init);
  }

  const response = await fetch(`${API_BASE_URL}${path}`, {
    ...init,
    headers: {
      "Content-Type": "application/json",
      ...(init?.headers || {}),
    },
    cache: "no-store",
  });

  if (!response.ok) {
    let message = response.statusText;
    try {
      const payload = await response.json();
      message = payload.detail || payload.error || JSON.stringify(payload);
    } catch {
      // ignore body parse errors
    }
    throw new Error(message);
  }

  return (await response.json()) as T;
}

export const api = {
  verifyGoogleToken: (idToken: string) =>
    request<GoogleAuthResponse>("/auth/google", {
      method: "POST",
      body: JSON.stringify({ id_token: idToken }),
    }),
  listBooks: () => request<Book[]>("/books"),
  getBook: (slug: string) => request<BookDetail>(`/books/${slug}`),
  listPlans: (email: string) => request<PlanSummary[]>(`/users/${encodeURIComponent(email)}/plans`),
  createPlan: (payload: CreatePlanPayload) =>
    request<Plan>("/plans", { method: "POST", body: JSON.stringify(payload) }),
  getPlan: (planId: string) => request<Plan>(`/plans/${planId}`),
  getPlanDay: (planId: string, dayNumber: number) =>
    request<PlanDayDetail>(`/plans/${planId}/days/${dayNumber}`),
};