const API_BASE = process.env.NEXT_PUBLIC_API_URL || ''; interface FetchOptions extends RequestInit { token?: string; } async function apiFetch( endpoint: string, options: FetchOptions = {} ): Promise { const { token, ...fetchOptions } = options; const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record || {}), }; if (token) { headers['Authorization'] = `Bearer ${token}`; } const res = await fetch(`${API_BASE}${endpoint}`, { ...fetchOptions, headers, credentials: 'include', }); if (!res.ok) { const error = await res.json().catch(() => ({ error: res.statusText })); throw new Error(error.error || `HTTP ${res.status}`); } return res.json(); } // ── Auth ───────────────────────────────────────────────────────────────────── export const authApi = { register: (data: { email: string; name: string; password: string }) => apiFetch<{ user: User; token: string }>('/api/auth/register', { method: 'POST', body: JSON.stringify(data), }), login: (data: { email: string; password: string }) => apiFetch<{ user: User; token: string }>('/api/auth/login', { method: 'POST', body: JSON.stringify(data), }), logout: () => apiFetch('/api/auth/logout', { method: 'POST' }), me: (token: string) => apiFetch<{ user: User }>('/api/auth/me', { token }), }; // ── Projects ───────────────────────────────────────────────────────────────── export const projectsApi = { list: (token: string) => apiFetch<{ projects: Project[] }>('/api/projects', { token }), create: (token: string, data: { name: string; description?: string }) => apiFetch<{ project: Project }>('/api/projects', { method: 'POST', body: JSON.stringify(data), token, }), get: (token: string, id: string) => apiFetch<{ project: Project }>(`/api/projects/${id}`, { token }), update: (token: string, id: string, data: { name?: string; description?: string }) => apiFetch<{ project: Project }>(`/api/projects/${id}`, { method: 'PUT', body: JSON.stringify(data), token, }), delete: (token: string, id: string) => apiFetch(`/api/projects/${id}`, { method: 'DELETE', token }), inviteMember: (token: string, projectId: string, email: string, role: string) => apiFetch(`/api/projects/${projectId}/members`, { method: 'POST', body: JSON.stringify({ email, role }), token, }), }; // ── Assets ─────────────────────────────────────────────────────────────────── export const assetsApi = { list: (token: string, projectId: string) => apiFetch<{ assets: Asset[] }>(`/api/assets?projectId=${projectId}`, { token }), get: (token: string, id: string) => apiFetch<{ asset: AssetWithComments }>(`/api/assets/${id}`, { token }), upload: (token: string, formData: FormData) => fetch(`${API_BASE}/api/assets/upload`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: formData, }).then(r => r.json()), updateStatus: (token: string, id: string, status: string) => apiFetch<{ asset: Asset }>(`/api/assets/${id}/status`, { method: 'PUT', body: JSON.stringify({ status }), token, }), delete: (token: string, id: string) => apiFetch(`/api/assets/${id}`, { method: 'DELETE', token }), }; // ── Comments ───────────────────────────────────────────────────────────────── export const commentsApi = { list: (token: string, assetId: string, resolved?: boolean) => { const q = resolved !== undefined ? `?resolved=${resolved}` : ''; return apiFetch<{ comments: Comment[] }>(`/api/assets/${assetId}/comments${q}`, { token }); }, create: (token: string, assetId: string, data: { content: string; timestamp?: number; annotations?: AnnotationData[]; parentId?: string; }) => apiFetch<{ comment: Comment }>(`/api/assets/${assetId}/comments`, { method: 'POST', body: JSON.stringify(data), token, }), resolve: (token: string, id: string) => apiFetch<{ comment: Comment }>(`/api/comments/${id}/resolve`, { method: 'PUT', token }), updateAnnotations: (token: string, id: string, annotations: AnnotationData[]) => apiFetch<{ comment: Comment }>(`/api/comments/${id}/annotations`, { method: 'PUT', body: JSON.stringify({ annotations }), token, }), delete: (token: string, id: string) => apiFetch(`/api/comments/${id}`, { method: 'DELETE', token }), }; // ── Users ──────────────────────────────────────────────────────────────────── export const usersApi = { list: (token: string) => apiFetch<{ users: AdminUser[] }>('/api/users', { token }), getMe: (token: string) => apiFetch<{ user: AdminUser }>('/api/users/me', { token }), updateMe: (token: string, data: { name?: string; avatarUrl?: string; currentPassword?: string; newPassword?: string; }) => apiFetch<{ user: AdminUser }>('/api/users/me', { method: 'PUT', body: JSON.stringify(data), token, }), updateRole: (token: string, id: string, role: string) => apiFetch<{ user: AdminUser }>(`/api/users/${id}/role`, { method: 'PUT', body: JSON.stringify({ role }), token, }), updateActive: (token: string, id: string, active: boolean) => apiFetch<{ user: AdminUser }>(`/api/users/${id}/active`, { method: 'PUT', body: JSON.stringify({ active }), token, }), deleteUser: (token: string, id: string) => apiFetch(`/api/users/${id}`, { method: 'DELETE', token }), }; // ── Types ───────────────────────────────────────────────────────────────────── export interface User { id: string; email: string; name: string; role: string; avatarUrl?: string | null; } export interface AdminUser extends User { active: boolean; createdAt: string; _count?: { memberships: number; comments: number; }; } export interface Project { id: string; name: string; description?: string | null; createdAt: string; members: Array<{ id: string; role: string; user: User }>; _count?: { assets: number }; } export interface Asset { id: string; projectId: string; title: string; filename: string; filePath: string; thumbnail?: string | null; hlsPath?: string | null; duration?: number | null; fps?: number; mimeType: string; status: string; createdAt: string; _count?: { comments: number }; } export interface AssetWithComments extends Asset { project: Project; comments: Comment[]; } export interface Comment { id: string; assetId: string; userId: string; content: string; timestamp?: number | null; annotations?: AnnotationData[] | null; resolved: boolean; parentId?: string | null; createdAt: string; user: User; replies?: Comment[]; } export interface AnnotationData { type: 'pen' | 'arrow' | 'rect' | 'ellipse' | 'text'; color: string; points?: [number, number][]; text?: string; boundingBox?: { x: number; y: number; width: number; height: number }; }