import { __ } from '@wordpress/i18n'; import type { ApiRoute, ClubLocation, Config, Item, Payload, Sport } from './types'; export class ApiError extends Error { constructor(message: string, public readonly field: string | null = null, public readonly conflicts?: { id: number; reference: string; customer: string; resource_name: string; starts_at: string; ends_at: string; }[]) { super(message); } } export class CatalogApi { constructor(public readonly config: Config) { } protected url(route: string, parameters: Record = {}): string { const [path, query] = route.split('?'); route = path; const url = new URL(this.config.restUrl); if (query) new URLSearchParams(query).forEach((v, k) => url.searchParams.set(k, v)); const restRoute = url.searchParams.get('rest_route'); if (restRoute !== null) url.searchParams.set('rest_route', restRoute + route); else url.pathname += route; url.searchParams.set('_locale', 'user'); Object.entries(parameters).forEach(([key, value]) => url.searchParams.set(key, value)); return url.toString(); } protected async request(route: string, method = 'GET', payload?: unknown, parameters?: Record): Promise { const response = await fetch(this.url(route, parameters), { method, credentials: 'same-origin', headers: { 'X-WP-Nonce': this.config.nonce, 'Content-Type': 'application/json' }, body: payload ? JSON.stringify(payload) : undefined, }); if (!response.ok) { const error = await response.json().catch(() => null); throw new ApiError(error?.message ?? __('The request failed. Please try again.', 'abece-booking'), error?.data?.field ?? null, error?.data?.conflicts); } return response; } async list(route: ApiRoute, page: number, perPage = 25, filters: Record = {}): Promise<{ items: T[]; pages: number; total: number; }> { const response = await this.request(route, 'GET', undefined, { ...filters, page: String(page), per_page: String(perPage) }); return { items: await response.json(), pages: Number(response.headers.get('X-WP-TotalPages') ?? 1), total: Number(response.headers.get('X-WP-Total') ?? 0), }; } async all(route: ApiRoute, filters: Record = {}): Promise { const items: T[] = []; let page = 1; let pages = 1; do { const result = await this.list(route, page++, 100, filters); items.push(...result.items); pages = result.pages; } while (page <= pages); return items; } async get(route: ApiRoute, id: number): Promise { return (await this.request(`${route}/${id}`)).json(); } async sports(): Promise { const sports: Sport[] = []; let page = 1; let pages = 1; do { const result = await this.list('sports', page++, 100); sports.push(...result.items); pages = result.pages; } while (page <= pages); return sports; } async save(route: ApiRoute, payload: Payload, id?: number): Promise { const response = await this.request(id ? `${route}/${id}` : route, id ? 'PATCH' : 'POST', payload); return response.json(); } async operation(route: string, payload?: unknown): Promise { return (await this.request(route, payload === undefined ? 'GET' : 'POST', payload)).json(); } async remove(route: ApiRoute, id: number): Promise { await this.request(`${route}/${id}`, 'DELETE'); } async resourceSuggestions(sport?: string): Promise<{ sort_order: number; name: string | null; }> { return (await this.request('resources/suggestions', 'GET', undefined, sport ? { sport_id: sport } : {})).json(); } async location(): Promise { return (await this.request('settings/location')).json(); } async searchLocation(address: string): Promise<{ results: { latitude: number; longitude: number; label: string; }[]; }> { return (await this.request('settings/location/search', 'POST', { address })).json(); } async saveLocation(payload: Payload): Promise { return (await this.request('settings/location', 'PATCH', payload)).json(); } }