nomadweb/frontend/src/lib/api.ts
2026-08-29 03:10:29 -05:00

104 lines
4.7 KiB
TypeScript

import type {
AuthResponse, BlogPost, BlogPostDetail, ChartData, CostResult, Destination, FAQ,
ProfileStats, SearchResult, Stats, SyncedUserPlan, Testimonial, Tool, Visa,
} from "./types";
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1";
// Fallback data when API is unavailable (build time / offline)
const FALLBACK_STATS: Stats = { countries: 195, avg_cost: 42, satisfaction: 87, total_nomads: "35.6M", active_today: 127 };
const FALLBACK_TICKER = { messages: ["🌴 小林 刚刚抵达清迈", "💻 Marco 在里斯本完成了 Sprint", "✈️ 今日新增 127 位游民出发"] };
async function fetchAPI<T>(path: string, options?: RequestInit): Promise<T> {
const isMutation = options?.method && options.method !== "GET";
try {
const res = await fetch(`${API_BASE}${path}`, {
...options,
headers: { "Content-Type": "application/json", ...options?.headers },
...(isMutation ? { cache: "no-store" } : { next: { revalidate: 60 } }),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
} catch (err) {
if (isMutation) throw err;
throw err;
}
}
async function fetchWithFallback<T>(path: string, fallback: T): Promise<T> {
try {
return await fetchAPI<T>(path);
} catch {
return fallback;
}
}
export const api = {
getStats: () => fetchWithFallback("/stats", FALLBACK_STATS),
getTicker: () => fetchWithFallback("/ticker", FALLBACK_TICKER),
getDestinations: (params?: { region?: string; search?: string; sort?: string }) => {
const q = new URLSearchParams();
if (params?.region) q.set("region", params.region);
if (params?.search) q.set("search", params.search);
if (params?.sort) q.set("sort", params.sort);
const qs = q.toString();
return fetchAPI<Destination[]>(`/destinations${qs ? `?${qs}` : ""}`);
},
getDestination: (slug: string) => fetchAPI<Destination>(`/destinations/${slug}`),
compareDestinations: (slugs: string[]) =>
fetchAPI<Destination[]>("/destinations/compare", {
method: "POST",
body: JSON.stringify({ slugs }),
}),
getVisas: () => fetchWithFallback<Visa[]>("/visas", []),
getFaqs: () => fetchWithFallback<FAQ[]>("/faqs", []),
getTestimonials: () => fetchWithFallback<Testimonial[]>("/testimonials", []),
getTools: () => fetchWithFallback<Tool[]>("/tools", []),
getBlog: () => fetchWithFallback<BlogPost[]>("/blog", []),
getBlogPost: (slug: string) => fetchAPI<BlogPostDetail>(`/blog/${slug}`),
subscribe: (email: string) =>
fetchAPI<{ success: boolean; message: string }>("/subscribe", {
method: "POST",
body: JSON.stringify({ email }),
}),
calculateCost: (data: { destination_slug: string; housing: string; months: number }) =>
fetchAPI<CostResult>("/calculator", { method: "POST", body: JSON.stringify(data) }),
getChartCost: () => fetchWithFallback<ChartData>("/charts/cost", { labels: [], datasets: [{ data: [] }] }),
getChartSpeed: () => fetchWithFallback<ChartData>("/charts/speed", { labels: [], datasets: [{ data: [] }] }),
getChartGrowth: () => fetchWithFallback<ChartData>("/charts/growth", { labels: [], datasets: [{ data: [] }] }),
getChartRadar: () => fetchWithFallback<ChartData>("/charts/radar", { labels: [], datasets: [] }),
// Auth
login: (email: string, password: string) =>
fetchAPI<AuthResponse>("/auth/login", { method: "POST", body: JSON.stringify({ email, password }) }),
register: (email: string, password: string, name: string) =>
fetchAPI<AuthResponse>("/auth/register", { method: "POST", body: JSON.stringify({ email, password, name }) }),
demoLogin: () => fetchAPI<AuthResponse>("/auth/demo"),
getFavorites: (token: string) =>
fetchAPI<{ slugs: string[] }>("/auth/favorites", { headers: { Authorization: `Bearer ${token}` } }),
toggleFavorite: (slug: string, token: string) =>
fetchAPI<{ slugs: string[] }>("/auth/favorites", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ destination_slug: slug }),
}),
getFavoriteDestinations: (token: string) =>
fetchAPI<Destination[]>("/auth/favorites/detail", {
headers: { Authorization: `Bearer ${token}` },
}),
getProfileStats: (token: string) =>
fetchAPI<ProfileStats>("/auth/profile/stats", {
headers: { Authorization: `Bearer ${token}` },
}),
getPlan: (token: string) =>
fetchAPI<SyncedUserPlan>("/auth/plan", {
headers: { Authorization: `Bearer ${token}` },
}),
savePlan: (token: string, plan: SyncedUserPlan) =>
fetchAPI<SyncedUserPlan>("/auth/plan", {
method: "PUT",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify(plan),
}),
search: (q: string) => fetchAPI<SearchResult[]>(`/search?q=${encodeURIComponent(q)}`),
};