'use client'; import useSWR from 'swr'; import { createApiClient, type MeDTO } from '@musicshare/shared'; export const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000'; export class ApiError extends Error { constructor( public status: number, message: string, ) { super(message); } } export async function api(path: string, init: { method?: string; body?: unknown } = {}): Promise { const res = await fetch(API_URL + path, { method: init.method ?? (init.body ? 'POST' : 'GET'), credentials: 'include', headers: init.body !== undefined ? { 'content-type': 'application/json' } : undefined, body: init.body !== undefined ? JSON.stringify(init.body) : undefined, }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new ApiError(res.status, (data as { error?: string }).error ?? res.statusText); return data as T; } /** Uploads one file as multipart form data (e.g. the profile picture). */ export async function uploadFile(path: string, file: File): Promise { const form = new FormData(); form.append('file', file); const res = await fetch(API_URL + path, { method: 'POST', credentials: 'include', body: form }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new ApiError(res.status, (data as { error?: string }).error ?? res.statusText); return data as T; } export const fetcher = (path: string) => api(path); export function useMe() { const { data, error, isLoading, mutate } = useSWR('/me', fetcher, { shouldRetryOnError: false, revalidateOnFocus: false, }); return { me: data, isLoading, loggedOut: error?.status === 401, mutate }; } /** The shared typed client, for calls not covered by `api` above. */ export const client = createApiClient({ baseUrl: API_URL, credentials: 'include' });