export const apiPrefix = import.meta.env.VITE_API_PREFIX || ""; export const apiBase = (import.meta.env.VITE_API_URL || "") + apiPrefix; /** Go JSON encodes nil slices as null — normalize before .map() */ export function itemsOrEmpty(items: T[] | null | undefined): T[] { return Array.isArray(items) ? items : []; } export function assetUrl(path: string | undefined | null) { if (!path) return ""; if (path.startsWith("http")) return path; return `${apiBase}${path}`; } export async function getJSON(path: string): Promise { const res = await fetch(`${apiBase}${path}`); if (!res.ok) throw new Error(await res.text()); return res.json() as Promise; } export async function getJSONAuth(path: string, token: string): Promise { const res = await fetch(`${apiBase}${path}`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(await res.text()); return res.json() as Promise; } export async function postJSON( path: string, body: unknown, token?: string, ): Promise { const headers: Record = { "Content-Type": "application/json", }; if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(`${apiBase}${path}`, { method: "POST", headers, body: JSON.stringify(body), }); if (!res.ok) throw new Error(await res.text()); return res.json() as Promise; } export async function postNoBody(path: string): Promise { const res = await fetch(`${apiBase}${path}`, { method: "POST" }); if (!res.ok) throw new Error(await res.text()); } export async function putJSON( path: string, body: unknown, token: string, ): Promise { const res = await fetch(`${apiBase}${path}`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(body), }); if (!res.ok) throw new Error(await res.text()); return res.json() as Promise; } export async function del(path: string, token: string) { const res = await fetch(`${apiBase}${path}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(await res.text()); } export async function uploadFile( file: File, token: string, ): Promise<{ url: string }> { const fd = new FormData(); fd.append("file", file); const res = await fetch(`${apiBase}/api/admin/upload`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: fd, }); if (!res.ok) throw new Error(await res.text()); return res.json(); } export type Category = { id: number; slug: string; name: string; description?: string; iconKey: string; sortOrder: number; }; export type Resource = { id: string; title: string; description?: string; type: string; language: string; categoryId: number; categorySlug: string; categoryName: string; coverImage?: string; fileUrl?: string; previewUrl?: string; externalUrl?: string; bodyText?: string; badgeLabel?: string; isDownloadable: boolean; isRecommended: boolean; publishedAt?: string; updatedAt: string; tags?: string[]; }; export type AdminResource = Resource & { isPublic: boolean; sortOrder: number; status: string; publishedAt?: string; viewCount?: number; downloadCount?: number; };