Files
chadebebe/lib/api.ts

359 lines
10 KiB
TypeScript

// API client for Next.js API routes
const API_BASE_URL = '/api';
export interface ApiError {
message: string;
status: number;
}
async function handleResponse<T>(response: Response): Promise<T> {
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 session(payload: { adm?: string; usr?: string }) {
const response = await fetch(`${API_BASE_URL}/auth/session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
});
return handleResponse<{ success: true; role: 'admin' | 'guest'; guestId: string | null }>(response);
},
async whoami() {
const response = await fetch(`${API_BASE_URL}/auth/session`, { credentials: 'include' });
return handleResponse<
| { role: 'admin'; guest: { id: string; name: string } | null }
| { role: 'guest'; guest: { id: string; name: string } }
| { role: 'none' }
>(response);
},
async logout() {
const response = await fetch(`${API_BASE_URL}/auth/logout`, {
method: 'POST',
credentials: 'include',
});
return handleResponse<{ success: true }>(response);
},
};
export interface Guest {
id: string;
name: string;
createdAt: string;
updatedAt: string;
}
export const guestsApi = {
async list() {
const response = await fetch(`${API_BASE_URL}/admin/guests`, { credentials: 'include' });
const data = await handleResponse<{ success: true; guests: Guest[] }>(response);
return data.guests;
},
async create(name: string) {
const response = await fetch(`${API_BASE_URL}/admin/guests`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ name }),
});
const data = await handleResponse<{ success: true; guest: Guest }>(response);
return data.guest;
},
async rename(id: string, name: string) {
const response = await fetch(`${API_BASE_URL}/admin/guests/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ name }),
});
const data = await handleResponse<{ success: true; guest: Guest }>(response);
return data.guest;
},
async delete(id: string) {
const response = await fetch(`${API_BASE_URL}/admin/guests/${id}`, {
method: 'DELETE',
credentials: 'include',
});
return handleResponse<{ success: true }>(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 ItemClaim {
id: string;
quantity: number;
note: string | null;
isPurchased: boolean;
claimedAt: string;
guest: { id: string; name: 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;
claims: ItemClaim[];
claimedQuantity: number;
remainingQuantity: number;
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<Wishlist>) {
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<Wishlist>) {
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<void>(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<Item>(response);
},
async create(wishlistId: string, data: Partial<Item>) {
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<Item>(response);
},
async update(id: string, data: Partial<Item>) {
const response = await fetch(`${API_BASE_URL}/items/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(data),
});
return handleResponse<Item>(response);
},
async delete(id: string) {
const response = await fetch(`${API_BASE_URL}/items/${id}`, {
method: 'DELETE',
credentials: 'include',
});
return handleResponse<void>(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, opts: { quantity?: number; 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({ quantity: opts.quantity ?? 1, note: opts.note }),
});
return handleResponse<{ success: true; item: Item }>(response);
},
async unclaim(itemId: string, opts: { guestId?: string } = {}) {
const response = await fetch(`${API_BASE_URL}/public/items/${itemId}/unclaim`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(opts.guestId ? { guestId: opts.guestId } : {}),
});
return handleResponse<{ success: true }>(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<ScrapedData>(response);
},
};
// Settings API
export interface Settings {
siteTitle: string;
homepageSubtext: 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<Settings>) {
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);
},
};