TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1"use client";23import type { ApiError } from "@spinza/shared";45export class ApiClientError extends Error {6 constructor(7 public status: number,8 public code: string,9 message: string,10 public details?: unknown,11 ) {12 super(message);13 }14}1516/** Browser fetch wrapper: same-origin `/api/*`, JSON in/out, typed errors. */17export async function api<T>(path: string, init: RequestInit & { json?: unknown } = {}): Promise<T> {18 const { json, headers, ...rest } = init;19 const res = await fetch(path, {20 ...rest,21 method: rest.method ?? (json !== undefined ? "POST" : "GET"),22 credentials: "same-origin",23 headers: { ...(json !== undefined ? { "Content-Type": "application/json" } : {}), ...(headers ?? {}) },24 body: json !== undefined ? JSON.stringify(json) : rest.body,25 }).catch(() => {26 throw new ApiClientError(0, "NETWORK", "Connection lost. Check your network and try again.");27 });28 const text = await res.text();29 let data: unknown = null;30 try {31 data = text ? JSON.parse(text) : null;32 } catch {33 data = null;34 }35 if (!res.ok) {36 const e = (data ?? {}) as Partial<ApiError>;37 throw new ApiClientError(res.status, e.error ?? "HTTP_" + res.status, e.message ?? (res.status >= 500 ? "Server unavailable. Please try again shortly." : "Request failed."), e.details);38 }39 return data as T;40}4142export const isUnauthorized = (e: unknown) => e instanceof ApiClientError && e.status === 401;43