export const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? (process.env.NODE_ENV === "development" ? "http://localhost:8080" : "/admin139"); function uiText(zhText: string, enText: string): string { if (typeof window === "undefined") return zhText; const lang = window.localStorage.getItem("csp.ui.language"); return lang === "en" ? enText : zhText; } type ApiEnvelope = | { ok: true; data?: T;[k: string]: unknown } | { ok: false; error?: string;[k: string]: unknown }; export async function apiFetch( path: string, init?: RequestInit, token?: string ): Promise { const headers = new Headers(init?.headers); if (token) headers.set("Authorization", `Bearer ${token}`); if (init?.body && !headers.has("Content-Type")) { headers.set("Content-Type", "application/json"); } const method = (init?.method ?? "GET").toUpperCase(); const retryable = method === "GET" || method === "HEAD"; let resp: Response; try { resp = await fetch(`${API_BASE}${path}`, { ...init, headers, cache: "no-store", }); } catch (err) { if (!retryable) { throw new Error( uiText( `网络请求失败,请检查后端服务或代理连接(${err instanceof Error ? err.message : String(err)})`, `Network request failed. Please check backend/proxy connectivity (${err instanceof Error ? err.message : String(err)}).` ) ); } await new Promise((resolve) => setTimeout(resolve, 400)); try { resp = await fetch(`${API_BASE}${path}`, { ...init, headers, cache: "no-store", }); } catch (retryErr) { throw new Error( uiText( `网络请求失败,请检查后端服务或代理连接(${retryErr instanceof Error ? retryErr.message : String(retryErr) })`, `Network request failed. Please check backend/proxy connectivity (${retryErr instanceof Error ? retryErr.message : String(retryErr) }).` ) ); } } const text = await resp.text(); let payload: unknown = null; if (text) { try { payload = JSON.parse(text) as unknown; } catch { payload = text; } } if (!resp.ok) { const msg = typeof payload === "object" && payload !== null && "error" in payload ? String((payload as { error?: unknown }).error ?? `HTTP ${resp.status}`) : `HTTP ${resp.status}`; throw new Error(msg); } if (typeof payload === "object" && payload !== null && "ok" in payload) { const env = payload as ApiEnvelope; if (!env.ok) { throw new Error(env.error ?? "request failed"); } if ("data" in env) return (env.data as T) ?? ({} as T); return payload as T; } return payload as T; } export interface RatingHistoryItem { type: string; created_at: number; change: number; note: string; } export async function listRatingHistory(limit: number = 100): Promise { return apiFetch(`/api/v1/me/rating-history?limit=${limit}`); }