321 lines
8.8 KiB
TypeScript
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);
|
|
},
|
|
};
|