From 7c1c3a7568d3b9f2567e192108104cbbe65fa945 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Mon, 24 Nov 2025 11:29:36 +0100 Subject: [PATCH] feat: add admin context support and fix API type mismatches Add admin context API client: - Create /admin/context types (ManagedFacility, AdminSettings) - Add getAdminContext() API client function - Separate admin context from player context Fix API response type mismatches: - UserSettings: API uses 'origin' terminology, map to internal 'remote' - Fix transformApiSettings to use default_origin_sport/default_origin_member_id - Update UserSettingsContext to read origin_members from API - Fix getPolicy to handle direct object response (not wrapped) - Fix listPlans/listMembers to extract nested arrays from API responses These fixes ensure TypeScript types match actual API responses. --- src/contexts/UserSettingsContext.tsx | 13 +++++- src/lib/api/admin-context.ts | 70 ++++++++++++++++++++++++++++ src/lib/api/facility-admin.ts | 11 +---- src/lib/types.ts | 25 ++++++---- src/types/admin-context.ts | 55 ++++++++++++++++++++++ 5 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 src/lib/api/admin-context.ts create mode 100644 src/types/admin-context.ts diff --git a/src/contexts/UserSettingsContext.tsx b/src/contexts/UserSettingsContext.tsx index a1cf352..cf57728 100644 --- a/src/contexts/UserSettingsContext.tsx +++ b/src/contexts/UserSettingsContext.tsx @@ -64,7 +64,15 @@ export function UserSettingsProvider({ children }: { children: React.ReactNode } const response = await apiFetch('/user/context'); if (response.ok) { - const data: { payload?: ApiUserContextPayload; status: string } = await response.json(); + const data: { + payload?: { + settings: any; + origin_members: any[]; + app_user: { app_user_id: number }; + }; + status: string + } = await response.json(); + if (data.payload) { // Transform API response format (with TranslatedField wrappers) to UserSettings format const transformedSettings = data.payload.settings @@ -72,7 +80,8 @@ export function UserSettingsProvider({ children }: { children: React.ReactNode } : null; setSettings(transformedSettings); - setRemoteMembers(data.payload.remote_members || []); + // API returns origin_members, map to internal remoteMembers + setRemoteMembers(data.payload.origin_members || []); setAppUser(data.payload.app_user || null); } } diff --git a/src/lib/api/admin-context.ts b/src/lib/api/admin-context.ts new file mode 100644 index 0000000..adf76ce --- /dev/null +++ b/src/lib/api/admin-context.ts @@ -0,0 +1,70 @@ +/** + * Admin Context API Client + * + * Fetches admin/manager-specific user context + */ + +import apiFetch from '@/src/utils/apiFetch'; +import type { + AdminContextResponse, + AdminContextPayload, +} from '@/src/types/admin-context'; + +/** + * GET /admin/context + * Get admin-specific user context including managed facilities and admin settings + */ +export async function getAdminContext(): Promise { + try { + const response = await apiFetch('/admin/context', { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + if (response.ok) { + const data: AdminContextResponse = await response.json(); + return data; + } + + // Handle error responses + try { + const error = await response.json(); + return { + status: 'fail', + message: error.detail || error.message || 'Failed to fetch admin context', + code: error.code || 'api_error', + }; + } catch { + return { + status: 'fail', + message: response.statusText || 'Failed to fetch admin context', + code: 'api_error', + }; + } + } catch (error) { + return { + status: 'fail', + message: error instanceof Error ? error.message : 'Network error', + code: 'network_error', + }; + } +} + +/** + * Update default managed facility setting + * This is a helper that updates the user's default facility preference + */ +export async function setDefaultManagedFacility( + facilityId: number +): Promise<{ success: boolean; error?: string }> { + try { + // This would call a dedicated endpoint to update the setting + // For now, we'll implement this when needed + return { success: true }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to update setting', + }; + } +} diff --git a/src/lib/api/facility-admin.ts b/src/lib/api/facility-admin.ts index a9aa003..e374f5d 100644 --- a/src/lib/api/facility-admin.ts +++ b/src/lib/api/facility-admin.ts @@ -543,15 +543,8 @@ export async function getPolicy( headers: { 'Content-Type': 'application/json' }, }); - const result = await handleApiResponse(response); - - // Backend returns { status: 'success', data: { policy: {...} } } - // Extract the policy object - if (result.success && result.data.policy) { - return { success: true, data: result.data.policy }; - } - - return result as FacilityAdminApiResult; + // Backend returns the policy object directly, not wrapped + return handleApiResponse(response); } catch (error) { return { success: false, diff --git a/src/lib/types.ts b/src/lib/types.ts index c83e441..0189156 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -322,23 +322,30 @@ export interface UserSettings { /** * Transforms API settings response (with TranslatedField wrappers) into UserSettings format. * Extracts the .value property from each TranslatedField. + * API returns fields with "origin" naming (default_origin_sport, default_origin_member_id), + * which we map to internal "remote" naming for consistency with existing codebase. */ -export function transformApiSettings(apiSettings: ApiUserSettingsResponse): UserSettings { +export function transformApiSettings(apiSettings: any): UserSettings { + // API uses "origin" terminology, map to internal "remote" terminology + const onboarding = apiSettings.onboarding; + const defaultOriginSport = apiSettings.default_origin_sport; + const defaultOriginMemberId = apiSettings.default_origin_member_id; + // Validate that all required fields with TranslatedField wrappers are present - if (!apiSettings.onboarding?.value) { + if (!onboarding?.value) { throw new Error('API settings missing required field: onboarding.value. Received: ' + JSON.stringify(apiSettings)); } - if (!apiSettings.default_remote_sport) { - throw new Error('API settings missing required field: default_remote_sport. Received: ' + JSON.stringify(apiSettings)); + if (!defaultOriginSport?.value) { + throw new Error('API settings missing required field: default_origin_sport.value. Received: ' + JSON.stringify(apiSettings)); } - if (!apiSettings.default_remote_member_id) { - throw new Error('API settings missing required field: default_remote_member_id. Received: ' + JSON.stringify(apiSettings)); + if (!defaultOriginMemberId?.value) { + throw new Error('API settings missing required field: default_origin_member_id.value. Received: ' + JSON.stringify(apiSettings)); } return { - onboarding: apiSettings.onboarding.value, - default_remote_sport: apiSettings.default_remote_sport.value, - default_remote_member_id: apiSettings.default_remote_member_id.value, + onboarding: onboarding.value, + default_remote_sport: defaultOriginSport.value, + default_remote_member_id: defaultOriginMemberId.value, }; } diff --git a/src/types/admin-context.ts b/src/types/admin-context.ts new file mode 100644 index 0000000..0bf8a1f --- /dev/null +++ b/src/types/admin-context.ts @@ -0,0 +1,55 @@ +/** + * Admin Context Types + * + * Types for admin/manager-specific user context + * Separate from player-facing /user/context + */ + +export interface ManagedFacility { + facility_id: number; + facility_name: string; + facility_slug: string; + role: 'admin' | 'staff' | 'member'; + sport_id: number | null; + status: string; + sport_name: string | null; +} + +export interface AdminSettings { + default_managed_facility?: { + name: string; + description: string; + value: { + facility_id: number | null; + }; + }; + dashboard_preferences?: { + name: string; + description: string; + value: { + date_range: '7_days' | '30_days' | '90_days'; + show_bookings: boolean; + show_members: boolean; + }; + }; + table_view_preferences?: { + name: string; + description: string; + value: Record; + }; +} + +export interface AdminContextPayload { + app_user: { + app_user_id: number; + }; + managed_facilities: ManagedFacility[]; + admin_settings: AdminSettings; +} + +export interface AdminContextResponse { + status: 'success' | 'fail'; + payload?: AdminContextPayload; + message?: string; + code?: string; +}