94 lines
4.4 KiB
TypeScript
94 lines
4.4 KiB
TypeScript
import type {
|
|
AuthResponse, BlogPost, BlogPostDetail, ChartData, CostResult, Destination, FAQ,
|
|
ProfileStats, SearchResult, Stats, 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}` },
|
|
}),
|
|
search: (q: string) => fetchAPI<SearchResult[]>(`/search?q=${encodeURIComponent(q)}`),
|
|
};
|