/** * Facility Admin API Client * * Handles all facility admin operations: membership plans, entitlements, members, policy * Build: 495 * Date: 2025-11-24 */ import type { MembershipPlan, CreatePlanRequest, UpdatePlanRequest, PlanEntitlements, SetEntitlementRequest, FacilityMember, AddMemberRequest, UpdateMemberRequest, MemberListFilters, FacilityPolicy, UpdatePolicyRequest, FacilityAdminError, FacilityAdminApiResult, PlanTemplate, } from '@/src/types/facility-admin'; import apiFetch from '@/src/utils/apiFetch'; // ============================================================================ // Helper Functions // ============================================================================ /** * Handle API response * Flask backend wraps responses in: { status: 'success'|'fail', data: {...} } */ async function handleApiResponse(response: Response): Promise> { if (response.ok) { // Handle 204 No Content - DELETE endpoints return empty body if (response.status === 204) { return { success: true, data: undefined as T }; } const json = await response.json(); // Flask returns { status: 'success', data: {...} } // Extract the actual data from the wrapper const data = json.data !== undefined ? json.data : json; return { success: true, data }; } // Handle error responses (RFC-7807) try { const error: FacilityAdminError = 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', code: 'internal_error', }, }; } } // ============================================================================ // Membership Plans // ============================================================================ /** * POST /admin/facilities/{facility_id}/plans * Create a new membership plan */ export async function createPlan( facilityId: number, request: CreatePlanRequest ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); 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 create plan', code: 'internal_error', }, }; } } /** * GET /admin/facilities/{facility_id}/plans * List all membership plans for a facility */ export async function listPlans( facilityId: number, filters?: { sport_id?: number; include_inactive?: boolean } ): Promise> { try { const params = new URLSearchParams(); if (filters?.sport_id !== undefined) params.set('sport_id', String(filters.sport_id)); if (filters?.include_inactive !== undefined) params.set('include_inactive', String(filters.include_inactive)); const queryString = params.toString(); const endpoint = `/admin/facilities/${facilityId}/plans${queryString ? `?${queryString}` : ''}`; const response = await apiFetch(endpoint, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); const result = await handleApiResponse(response); // Backend returns { plans: [...] } - extract the plans array if (result.success && result.data.plans) { return { success: true, data: result.data.plans }; } return result as FacilityAdminApiResult; } catch (error) { return { success: false, error: { type: 'about:blank', title: 'Network Error', status: 0, detail: error instanceof Error ? error.message : 'Failed to list plans', code: 'internal_error', }, }; } } /** * GET /admin/facilities/{facility_id}/plans/{plan_id} * Get a single membership plan */ export async function getPlan( facilityId: number, planId: number ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans/${planId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); 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 get plan', code: 'internal_error', }, }; } } /** * PATCH /admin/facilities/{facility_id}/plans/{plan_id} * Update a membership plan (partial update) */ export async function updatePlan( facilityId: number, planId: number, request: UpdatePlanRequest ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans/${planId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); 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 update plan', code: 'internal_error', }, }; } } /** * DELETE /admin/facilities/{facility_id}/plans/{plan_id} * Delete a membership plan (soft delete by default) */ export async function deletePlan( facilityId: number, planId: number, hardDelete: boolean = false ): Promise> { try { const params = new URLSearchParams(); if (hardDelete) params.set('hard_delete', 'true'); const queryString = params.toString(); const endpoint = `/admin/facilities/${facilityId}/plans/${planId}${queryString ? `?${queryString}` : ''}`; const response = await apiFetch(endpoint, { method: 'DELETE', }); 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 delete plan', code: 'internal_error', }, }; } } // ============================================================================ // Plan Templates // ============================================================================ /** * GET /admin/plan-templates * Get all available plan templates */ export async function listPlanTemplates( filters?: { sport_id?: number } ): Promise> { try { const params = new URLSearchParams(); if (filters?.sport_id !== undefined) params.set('sport_id', String(filters.sport_id)); const queryString = params.toString(); const endpoint = `/admin/plan-templates${queryString ? `?${queryString}` : ''}`; const response = await apiFetch(endpoint, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); const result = await handleApiResponse(response); // Backend returns { templates: [...] } - extract the templates array if (result.success && result.data.templates) { return { success: true, data: result.data.templates }; } return result as FacilityAdminApiResult; } catch (error) { return { success: false, error: { type: 'about:blank', title: 'Network Error', status: 0, detail: error instanceof Error ? error.message : 'Failed to list templates', code: 'internal_error', }, }; } } // ============================================================================ // Entitlements // ============================================================================ /** * GET /admin/facilities/{facility_id}/plans/{plan_id}/entitlements * Get all entitlements for a plan */ export async function getEntitlements( facilityId: number, planId: number ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans/${planId}/entitlements`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); 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 get entitlements', code: 'internal_error', }, }; } } /** * PUT /admin/facilities/{facility_id}/plans/{plan_id}/entitlements * Replace all entitlements for a plan (bulk update) */ export async function updateEntitlements( facilityId: number, planId: number, entitlements: PlanEntitlements ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans/${planId}/entitlements`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entitlements), }); 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 update entitlements', code: 'internal_error', }, }; } } /** * PUT /admin/facilities/{facility_id}/plans/{plan_id}/entitlements/{key} * Set or update a single entitlement */ export async function setEntitlement( facilityId: number, planId: number, key: string, request: SetEntitlementRequest ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans/${planId}/entitlements/${key}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); return handleApiResponse<{ key: string; value: string | number | boolean }>(response); } catch (error) { return { success: false, error: { type: 'about:blank', title: 'Network Error', status: 0, detail: error instanceof Error ? error.message : 'Failed to set entitlement', code: 'internal_error', }, }; } } /** * DELETE /admin/facilities/{facility_id}/plans/{plan_id}/entitlements/{key} * Delete a specific entitlement */ export async function deleteEntitlement( facilityId: number, planId: number, key: string ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/plans/${planId}/entitlements/${key}`, { method: 'DELETE', }); 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 delete entitlement', code: 'internal_error', }, }; } } // ============================================================================ // Members // ============================================================================ /** * POST /admin/facilities/{facility_id}/members * Add a member to a facility */ export async function addMember( facilityId: number, request: AddMemberRequest ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/members`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); 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 add member', code: 'internal_error', }, }; } } /** * GET /admin/facilities/{facility_id}/members * List all members for a facility */ export async function listMembers( facilityId: number, filters?: MemberListFilters ): Promise> { try { const params = new URLSearchParams(); if (filters?.sport_id !== undefined) params.set('sport_id', String(filters.sport_id)); if (filters?.status) params.set('status', filters.status); if (filters?.role) params.set('role', filters.role); if (filters?.plan_id !== undefined) params.set('plan_id', String(filters.plan_id)); const queryString = params.toString(); const endpoint = `/admin/facilities/${facilityId}/members${queryString ? `?${queryString}` : ''}`; const response = await apiFetch(endpoint, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); const result = await handleApiResponse(response); // Backend returns { members: [...] } - extract the members array if (result.success && result.data.members) { return { success: true, data: result.data.members }; } return result as FacilityAdminApiResult; } catch (error) { return { success: false, error: { type: 'about:blank', title: 'Network Error', status: 0, detail: error instanceof Error ? error.message : 'Failed to list members', code: 'internal_error', }, }; } } /** * GET /admin/facilities/{facility_id}/members/{member_id} * Get a single member */ export async function getMember( facilityId: number, memberId: number ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/members/${memberId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); 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 get member', code: 'internal_error', }, }; } } /** * PATCH /admin/facilities/{facility_id}/members/{member_id} * Update a member (partial update) */ export async function updateMember( facilityId: number, memberId: number, request: UpdateMemberRequest ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/members/${memberId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); 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 update member', code: 'internal_error', }, }; } } /** * DELETE /admin/facilities/{facility_id}/members/{member_id} * Delete a member (soft delete by default) */ export async function deleteMember( facilityId: number, memberId: number, hardDelete: boolean = false ): Promise> { try { const params = new URLSearchParams(); if (hardDelete) params.set('hard_delete', 'true'); const queryString = params.toString(); const endpoint = `/admin/facilities/${facilityId}/members/${memberId}${queryString ? `?${queryString}` : ''}`; const response = await apiFetch(endpoint, { method: 'DELETE', }); 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 delete member', code: 'internal_error', }, }; } } // ============================================================================ // Policy // ============================================================================ /** * GET /admin/facilities/{facility_id}/policy * Get facility policy (creates default if doesn't exist) */ export async function getPolicy( facilityId: number ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/policy`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); // Backend returns the policy object directly, not wrapped 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 get policy', code: 'internal_error', }, }; } } /** * PATCH /admin/facilities/{facility_id}/policy * Update facility policy (partial update, upsert) */ export async function updatePolicy( facilityId: number, request: UpdatePolicyRequest ): Promise> { try { const response = await apiFetch(`/admin/facilities/${facilityId}/policy`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); 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 update policy', code: 'internal_error', }, }; } }