// API client for Next.js API routes const API_BASE_URL = '/api'; export interface ApiError { message: string; status: number; } async function handleResponse(response: Response): Promise { if (!response.ok) { const error = await response.json().catch(() => ({ error: 'An error occurred' })); // Extract error message from various possible response formats const message = error.error || error.message || 'An error occurred'; console.error('API Error:', { status: response.status, message, fullError: error }); throw { message, status: response.status } as ApiError; } // Handle empty responses const text = await response.text(); return text ? JSON.parse(text) : ({} as T); } // Auth API export const authApi = { async login(username: string, password: string) { const response = await fetch(`${API_BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ username, password }), }); return handleResponse<{ accessToken: string; refreshToken: string }>(response); }, async logout() { const response = await fetch(`${API_BASE_URL}/auth/logout`, { method: 'POST', credentials: 'include', }); return handleResponse(response); }, async refresh() { const response = await fetch(`${API_BASE_URL}/auth/refresh`, { method: 'POST', credentials: 'include', }); return handleResponse<{ accessToken: string }>(response); }, async me() { const response = await fetch(`${API_BASE_URL}/auth/me`, { credentials: 'include', }); return handleResponse<{ username: string }>(response); }, }; // Wishlist types export interface Wishlist { id: string; name: string; slug: string; description: string | null; preferences: string | null; imageUrl: string | null; isPublic: boolean; sortOrder: number; createdAt: string; updatedAt: string; } export interface Item { id: string; wishlistId: string; name: string; description: string | null; price: number | null; currency: string; quantity: number; imageUrl: string | null; purchaseUrls: Array<{ label: string; url: string }> | null; isArchived: boolean; claimedByName: string | null; claimedByNote: string | null; claimedAt: string | null; isPurchased: boolean; sortOrder: number; createdAt: string; updatedAt: string; } // Wishlists API export const wishlistsApi = { async getAll() { const response = await fetch(`${API_BASE_URL}/wishlists`, { credentials: 'include', }); const data = await handleResponse<{ success: boolean; wishlists: Wishlist[] }>(response); return data.wishlists; }, async getAllPublic() { const response = await fetch(`${API_BASE_URL}/public/wishlists`, { credentials: 'include', }); const data = await handleResponse<{ success: boolean; wishlists: Wishlist[] }>(response); return data.wishlists; }, async getOne(id: string) { const response = await fetch(`${API_BASE_URL}/wishlists/${id}`, { credentials: 'include', }); const result = await handleResponse<{ success: boolean; wishlist: Wishlist }>(response); return result.wishlist; }, async getBySlug(slug: string) { const response = await fetch(`${API_BASE_URL}/${slug}`, { credentials: 'include', }); const result = await handleResponse<{ success: boolean; wishlist: Wishlist }>(response); return result.wishlist; }, async create(data: Partial) { const response = await fetch(`${API_BASE_URL}/wishlists`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(data), }); const result = await handleResponse<{ success: boolean; wishlist: Wishlist }>(response); return result.wishlist; }, async update(id: string, data: Partial) { const response = await fetch(`${API_BASE_URL}/wishlists/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(data), }); const result = await handleResponse<{ success: boolean; wishlist: Wishlist }>(response); return result.wishlist; }, async delete(id: string) { const response = await fetch(`${API_BASE_URL}/wishlists/${id}`, { method: 'DELETE', credentials: 'include', }); return handleResponse(response); }, async reorder(id: string, newSortOrder: number) { const response = await fetch(`${API_BASE_URL}/wishlists/${id}/reorder`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ newSortOrder }), }); const result = await handleResponse<{ success: boolean; wishlist: Wishlist }>(response); return result.wishlist; }, }; // Items API export const itemsApi = { async getAll(wishlistId: string) { const response = await fetch(`${API_BASE_URL}/wishlists/${wishlistId}/items`, { credentials: 'include', }); const result = await handleResponse<{ success: boolean; items: Item[] }>(response); return result.items; }, async getOne(id: string) { const response = await fetch(`${API_BASE_URL}/items/${id}`, { credentials: 'include', }); return handleResponse(response); }, async create(wishlistId: string, data: Partial) { const response = await fetch(`${API_BASE_URL}/wishlists/${wishlistId}/items`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(data), }); return handleResponse(response); }, async update(id: string, data: Partial) { const response = await fetch(`${API_BASE_URL}/items/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(data), }); return handleResponse(response); }, async delete(id: string) { const response = await fetch(`${API_BASE_URL}/items/${id}`, { method: 'DELETE', credentials: 'include', }); return handleResponse(response); }, async reorder(id: string, newSortOrder: number) { const response = await fetch(`${API_BASE_URL}/items/${id}/reorder`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ newSortOrder }), }); const result = await handleResponse<{ success: boolean; item: Item }>(response); return result.item; }, }; // Claiming API (public) export const claimingApi = { async claim(itemId: string, name?: string, note?: string) { const response = await fetch(`${API_BASE_URL}/public/items/${itemId}/claim`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ name, note }), }); return handleResponse<{ claimToken: string; message: string }>(response); }, async unclaim(itemId: string) { const response = await fetch(`${API_BASE_URL}/public/items/${itemId}/unclaim`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', }); return handleResponse<{ success: boolean; message: string }>(response); }, }; // Scraping API export interface ScrapedData { title?: string; description?: string; price?: number; currency?: string; imageUrl?: string; } export const scrapingApi = { async scrapeUrl(url: string) { const response = await fetch(`${API_BASE_URL}/scrape`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ url }), }); return handleResponse(response); }, }; // Settings API export interface Settings { siteTitle: string; homepageSubtext: string; passwordLockEnabled?: boolean; passwordLock?: string; } export const settingsApi = { async getSettings() { const response = await fetch(`${API_BASE_URL}/settings`, { credentials: 'include', }); const result = await handleResponse<{ success: boolean; settings: Settings }>(response); return result.settings; }, async updateSettings(settings: Partial) { const response = await fetch(`${API_BASE_URL}/settings`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(settings), }); return handleResponse<{ success: boolean; message: string }>(response); }, };