Files
meezi/web/finder/src/lib/api.ts
T

101 lines
4.0 KiB
TypeScript
Raw Normal View History

import type {
ApiResponse,
CafeDiscoverDto,
CafePublicDto,
PublicMenuDto,
CafeReviewDto,
NlpHints,
DiscoverFilters,
} from "@/lib/types";
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "https://api.meezi.ir";
async function get<T>(path: string, opts?: RequestInit): Promise<T | null> {
try {
const res = await fetch(`${API_URL}${path}`, {
next: { revalidate: 60 },
...opts,
});
if (!res.ok) return null;
const json = (await res.json()) as ApiResponse<T>;
return json.success ? json.data : null;
} catch {
return null;
}
}
// ── Discover / search ─────────────────────────────────────────────────────────
export async function discoverCafes(
filters: DiscoverFilters
): Promise<CafeDiscoverDto[]> {
const params = new URLSearchParams();
if (filters.city) params.set("city", filters.city);
if (filters.q) params.set("q", filters.q);
if (filters.minRating) params.set("minRating", String(filters.minRating));
if (filters.sort) params.set("sort", filters.sort);
if (filters.themes?.length) params.set("themes", filters.themes.join(","));
if (filters.vibes?.length) params.set("vibes", filters.vibes.join(","));
if (filters.occasions?.length) params.set("occasions", filters.occasions.join(","));
if (filters.spaceFeatures?.length) params.set("spaceFeatures", filters.spaceFeatures.join(","));
if (filters.noise) params.set("noise", filters.noise);
if (filters.priceTier) params.set("priceTier", filters.priceTier);
if (filters.size) params.set("size", filters.size);
if (filters.openNow) params.set("openNow", "true");
const qs = params.toString();
const result = await get<CafeDiscoverDto[]>(
`/api/public/discover${qs ? `?${qs}` : ""}`,
{ next: { revalidate: 30 } }
);
return result ?? [];
}
// ── Individual cafe ───────────────────────────────────────────────────────────
export async function getCafe(slug: string): Promise<CafePublicDto | null> {
return get<CafePublicDto>(`/api/public/cafes/${slug}`, {
next: { revalidate: 300 },
});
}
// ── Menu ──────────────────────────────────────────────────────────────────────
export async function getCafeMenu(slug: string): Promise<PublicMenuDto | null> {
return get<PublicMenuDto>(`/api/public/cafes/${slug}/menu`, {
next: { revalidate: 300 },
});
}
// ── Reviews ───────────────────────────────────────────────────────────────────
export async function getCafeReviews(
slug: string,
page = 1
): Promise<CafeReviewDto[]> {
const result = await get<CafeReviewDto[]>(
`/api/public/cafes/${slug}/reviews?page=${page}&pageSize=10`,
{ next: { revalidate: 120 } }
);
return result ?? [];
}
// ── NLP parse (server-side only; for ISR hint pre-population) ─────────────────
export async function nlpParse(q: string): Promise<NlpHints | null> {
return get<NlpHints>(
`/api/public/discover/nlp-parse?q=${encodeURIComponent(q)}`,
{ cache: "no-store" }
);
}
// ── Slugs for static generation ───────────────────────────────────────────────
export async function getAllCafeSlugs(): Promise<string[]> {
// Fetch all cafes without filters to get slugs for static generation
const cafes = await get<CafeDiscoverDto[]>("/api/public/discover?requireProfile=false", {
next: { revalidate: 3600 },
});
return cafes?.map((c) => c.slug) ?? [];
}