Spaces:
Runtime error
Runtime error
| 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}`), | |
| }; | |