Initial commit
This commit is contained in:
312
lib/api.ts
Normal file
312
lib/api.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
// 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 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<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);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user