Files
chadebebe/lib/api.ts

321 lines
8.8 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 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<void>(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 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, 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<ScrapedData>(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<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);
},
};