/** * Admin Clubs API Client * Handles all /admin/clubs endpoints * * Based on: docs/VENUE_ADMIN_DESIGN.md (Phase 0) * Owner: Frontend Faye * Created: 2025-11-05 */ import type { AdminClubsResponse, AdminClubDetail, AdminApiResult, AdminApiError, } from '@/src/types/admin-api'; import { getPathnameLocale } from '@/src/utils/getLocale'; // ============================================================================ // Configuration // ============================================================================ const API_BASE_URL = process.env.NEXT_PUBLIC_PYTHON_API_URL; if (!API_BASE_URL) { throw new Error('NEXT_PUBLIC_PYTHON_API_URL environment variable is not set'); } // ============================================================================ // Helpers // ============================================================================ /** * Add locale and timezone headers to requests */ function getLocaleHeaders(): Record { const locale = typeof window !== 'undefined' ? getPathnameLocale(window.location.pathname) || 'en-US' : 'en-US'; const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; return { 'X-Locale': locale, 'X-Timezone': timezone, }; } // ============================================================================ // Error Handling // ============================================================================ async function handleApiResponse(response: Response): Promise> { if (response.ok) { const data = await response.json(); return { success: true, data }; } // Handle error responses try { const error: AdminApiError = await response.json(); return { success: false, error }; } catch { // Fallback for non-JSON errors return { success: false, error: { type: 'about:blank', title: 'API Error', status: response.status, detail: response.statusText || 'An unexpected error occurred', instance: response.url, code: `HTTP_${response.status}`, }, }; } } // ============================================================================ // API Functions // ============================================================================ /** * GET /admin/clubs * Lists all clubs the authenticated user can manage * * @param cookieHeader - Optional cookie header to forward (for SSR) * @returns Array of clubs or error */ export async function getAdminClubs(cookieHeader?: string): Promise> { try { const headers: Record = { 'Content-Type': 'application/json', ...getLocaleHeaders(), }; // Forward cookies for server-side rendering if (cookieHeader) { headers['Cookie'] = cookieHeader; } const response = await fetch(`${API_BASE_URL}/admin/clubs`, { method: 'GET', headers, credentials: cookieHeader ? 'omit' : 'include', // Use 'omit' when manually setting Cookie header }); return handleApiResponse(response); } catch (error) { return { success: false, error: { type: 'about:blank', title: 'Network Error', status: 0, detail: error instanceof Error ? error.message : 'Failed to connect to API', instance: `${API_BASE_URL}/admin/clubs`, code: 'NETWORK_ERROR', }, }; } } /** * GET /admin/clubs/{club_id} * Gets detailed information about a specific club * * @param clubId - The club ID to fetch * @param cookieHeader - Optional cookie header to forward (for SSR) * @returns Club details or error */ export async function getAdminClubDetail( clubId: number, cookieHeader?: string ): Promise> { try { const headers: Record = { 'Content-Type': 'application/json', ...getLocaleHeaders(), }; // Forward cookies for server-side rendering if (cookieHeader) { headers['Cookie'] = cookieHeader; } const response = await fetch(`${API_BASE_URL}/admin/clubs/${clubId}`, { method: 'GET', headers, credentials: cookieHeader ? 'omit' : 'include', // Use 'omit' when manually setting Cookie header }); return handleApiResponse(response); } catch (error) { return { success: false, error: { type: 'about:blank', title: 'Network Error', status: 0, detail: error instanceof Error ? error.message : 'Failed to connect to API', instance: `${API_BASE_URL}/admin/clubs/${clubId}`, code: 'NETWORK_ERROR', }, }; } } // ============================================================================ // Mock Data (for local development until staging stubs are ready) // ============================================================================ export const MOCK_CLUBS: AdminClubsResponse = [ { club_id: 1, name: 'Central Padel Geneva', timezone: 'Europe/Zurich', courts: 4, }, { club_id: 2, name: 'Riverside Tennis Lausanne', timezone: 'Europe/Zurich', courts: 6, }, ]; export const MOCK_CLUB_DETAIL: AdminClubDetail = { club: { club_id: 1, name: 'Central Padel Geneva', timezone: 'Europe/Zurich', }, courts: [ { court_id: 1, name: 'Court 1', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }, { court_id: 2, name: 'Court 2', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }, { court_id: 3, name: 'Court 3', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }, ], slot_definitions: [], upcoming_slots: [], provider: { remote_type: 'local', capabilities: { manages_slot_storage: true, supports_payment_verification: false, }, }, };