import { BookingError } from '@/src/lib/errors'; import { BookingResponseOk } from '@/src/lib/types'; type ApiClient = (url: string, options?: RequestInit) => Promise; /** * Service layer for booking-related API operations * Centralizes all API calls and error handling */ export class BookingService { constructor(private readonly apiClient: ApiClient) {} /** * Get booking details by slot ID */ async getBooking(slotId: string): Promise { try { const response = await this.apiClient(`/booking/${encodeURIComponent(slotId)}`); if (!response.ok) { if (response.status === 404) { throw BookingError.notFound(slotId); } const data = await response.json().catch(() => ({ message: 'Failed to load booking' })); throw BookingError.serverError(data.message || 'Failed to load booking'); } return response.json(); } catch (error) { if (error instanceof BookingError) throw error; throw BookingError.networkError('Network error while loading booking'); } } /** * Approve a swap approval request */ async approveSwap(slotId: string, approvalId: number): Promise { try { const response = await this.apiClient( `/booking/${slotId}/swap-approval/${approvalId}`, { method: 'PATCH' } ); if (!response.ok) { const data = await response.json().catch(() => ({ message: 'Failed to approve swap' })); if (response.status === 400) { throw BookingError.badRequest(data.message || 'Invalid approval request'); } throw BookingError.serverError(data.message || 'Failed to approve swap'); } } catch (error) { if (error instanceof BookingError) throw error; throw BookingError.networkError('Network error while approving swap'); } } /** * Cancel a swap group */ async cancelSwap(slotId: string, swapGroupId: string): Promise { try { const response = await this.apiClient( `/booking/${slotId}/swap-group/${swapGroupId}`, { method: 'DELETE' } ); if (!response.ok) { const data = await response.json().catch(() => ({ message: 'Failed to cancel swap' })); if (response.status === 403) { throw BookingError.forbidden(data.message || 'Only the initiator can cancel a swap'); } if (response.status === 400) { throw BookingError.badRequest(data.message || 'Invalid cancellation request'); } throw BookingError.serverError(data.message || 'Failed to cancel swap'); } } catch (error) { if (error instanceof BookingError) throw error; throw BookingError.networkError('Network error while cancelling swap'); } } /** * Submit or update match score */ async submitMatchScore( slotId: string, sets: number[][], method: 'POST' | 'PATCH' = 'POST' ): Promise { try { const response = await this.apiClient( `/booking/${slotId}/match/result`, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sets }) } ); if (!response.ok) { const data = await response.json().catch(() => ({ message: 'Failed to submit score' })); if (response.status === 400) { throw BookingError.badRequest(data.message || 'Invalid score data'); } throw BookingError.serverError(data.message || 'Failed to submit score'); } } catch (error) { if (error instanceof BookingError) throw error; throw BookingError.networkError('Network error while submitting score'); } } /** * Change match type */ async changeMatchType( slotId: string, matchTypeId: number, forced: boolean = false ): Promise { try { const response = await this.apiClient( `/booking/${slotId}/match-type`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ match_type_id: matchTypeId, forced }) } ); if (!response.ok) { const data = await response.json().catch(() => ({ message: 'Failed to change match type' })); if (response.status === 400) { throw BookingError.badRequest(data.message || 'Invalid match type change request'); } if (response.status === 403) { throw BookingError.forbidden(data.message || 'Not authorized to change match type'); } throw BookingError.serverError(data.message || 'Failed to change match type'); } } catch (error) { if (error instanceof BookingError) throw error; throw BookingError.networkError('Network error while changing match type'); } } }