import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { Switch } from '@/components/ui/switch'; import { Skeleton } from '@/components/ui/skeleton'; import { useAuth } from '@/hooks/useAuth'; import { useProfile } from '@/hooks/useProfile'; import { useUnitPreferences } from '@/hooks/useUnitPreferences'; import { supabase } from '@/integrations/supabase/client'; import { handleError, handleSuccess, AppError } from '@/lib/errorHandler'; import { logger } from '@/lib/logger'; import { MapPin, Calendar, Accessibility, Ruler } from 'lucide-react'; import type { LocationFormData, AccessibilityOptions, ParkOption } from '@/types/location'; import { locationFormSchema, accessibilityOptionsSchema, parkOptionSchema, DEFAULT_ACCESSIBILITY_OPTIONS, COMMON_TIMEZONES } from '@/lib/locationValidation'; export function LocationTab() { const { user } = useAuth(); const { data: profile, refreshProfile } = useProfile(user?.id); const { preferences: unitPreferences, updatePreferences: updateUnitPreferences } = useUnitPreferences(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [parks, setParks] = useState([]); const [accessibility, setAccessibility] = useState(DEFAULT_ACCESSIBILITY_OPTIONS); const form = useForm({ resolver: zodResolver(locationFormSchema), defaultValues: { preferred_pronouns: profile?.preferred_pronouns || null, timezone: profile?.timezone || 'UTC', preferred_language: profile?.preferred_language || 'en', personal_location: profile?.personal_location || null, home_park_id: profile?.home_park_id || null } }); useEffect(() => { if (user && profile) { form.reset({ preferred_pronouns: profile.preferred_pronouns || null, timezone: profile.timezone || 'UTC', preferred_language: profile.preferred_language || 'en', personal_location: profile.personal_location || null, home_park_id: profile.home_park_id || null }); } }, [profile, form]); useEffect(() => { if (user) { fetchParks(); fetchAccessibilityPreferences(); } }, [user]); const fetchParks = async () => { if (!user) return; try { const { data, error } = await supabase .from('parks') .select('id, name, locations(city, state_province, country)') .order('name'); if (error) { logger.error('Failed to fetch parks list', { userId: user.id, action: 'fetch_parks', error: error.message, errorCode: error.code }); throw error; } const validatedParks = (data || []) .map(park => { try { return parkOptionSchema.parse({ id: park.id, name: park.name, location: park.locations ? { city: park.locations.city, state_province: park.locations.state_province, country: park.locations.country } : undefined }); } catch { return null; } }) .filter((park): park is ParkOption => park !== null); setParks(validatedParks); logger.info('Parks list loaded', { userId: user.id, action: 'fetch_parks', count: validatedParks.length }); } catch (error: unknown) { logger.error('Error fetching parks', { userId: user.id, action: 'fetch_parks', error: error instanceof Error ? error.message : String(error) }); handleError(error, { action: 'Load parks list', userId: user.id }); } }; const fetchAccessibilityPreferences = async () => { if (!user) return; try { const { data, error } = await supabase .from('user_preferences') .select('accessibility_options') .eq('user_id', user.id) .maybeSingle(); if (error && error.code !== 'PGRST116') { logger.error('Failed to fetch accessibility preferences', { userId: user.id, action: 'fetch_accessibility_preferences', error: error.message, errorCode: error.code }); throw error; } if (data?.accessibility_options) { const validated = accessibilityOptionsSchema.parse(data.accessibility_options); setAccessibility(validated); } logger.info('Accessibility preferences loaded', { userId: user.id, action: 'fetch_accessibility_preferences' }); } catch (error: unknown) { logger.error('Error fetching accessibility preferences', { userId: user.id, action: 'fetch_accessibility_preferences', error: error instanceof Error ? error.message : String(error) }); handleError(error, { action: 'Load accessibility preferences', userId: user.id }); } finally { setLoading(false); } }; const onSubmit = async (data: LocationFormData) => { if (!user) return; setSaving(true); try { const validatedData = locationFormSchema.parse(data); const validatedAccessibility = accessibilityOptionsSchema.parse(accessibility); const previousProfile = { personal_location: profile?.personal_location, home_park_id: profile?.home_park_id, timezone: profile?.timezone, preferred_language: profile?.preferred_language, preferred_pronouns: profile?.preferred_pronouns }; const { error: profileError } = await supabase .from('profiles') .update({ preferred_pronouns: validatedData.preferred_pronouns || null, timezone: validatedData.timezone, preferred_language: validatedData.preferred_language, personal_location: validatedData.personal_location || null, home_park_id: validatedData.home_park_id || null, updated_at: new Date().toISOString() }) .eq('user_id', user.id); if (profileError) { logger.error('Failed to update profile', { userId: user.id, action: 'update_profile_location', error: profileError.message, errorCode: profileError.code }); throw profileError; } const { error: accessibilityError } = await supabase .from('user_preferences') .update({ accessibility_options: validatedAccessibility, updated_at: new Date().toISOString() }) .eq('user_id', user.id); if (accessibilityError) { logger.error('Failed to update accessibility preferences', { userId: user.id, action: 'update_accessibility_preferences', error: accessibilityError.message, errorCode: accessibilityError.code }); throw accessibilityError; } await updateUnitPreferences(unitPreferences); await supabase.from('profile_audit_log').insert([{ user_id: user.id, changed_by: user.id, action: 'location_info_updated', changes: JSON.parse(JSON.stringify({ previous: { profile: previousProfile, accessibility: DEFAULT_ACCESSIBILITY_OPTIONS }, updated: { profile: validatedData, accessibility: validatedAccessibility }, timestamp: new Date().toISOString() })) }]); await refreshProfile(); logger.info('Location and info settings updated', { userId: user.id, action: 'update_location_info' }); handleSuccess( 'Settings saved', 'Your location, personal information, accessibility, and unit preferences have been updated.' ); } catch (error: unknown) { logger.error('Error saving location settings', { userId: user.id, action: 'save_location_settings', error: error instanceof Error ? error.message : String(error) }); if (error instanceof z.ZodError) { handleError( new AppError( 'Invalid settings', 'VALIDATION_ERROR', error.issues.map(i => i.message).join(', ') ), { action: 'Validate location settings', userId: user.id } ); } else { handleError(error, { action: 'Save location settings', userId: user.id }); } } finally { setSaving(false); } }; const updateAccessibility = (key: keyof AccessibilityOptions, value: any) => { setAccessibility(prev => ({ ...prev, [key]: value })); }; if (loading) { return (
); } return (
{/* Location Settings + Personal Information Grid */}
{/* Location Settings */}
Location Settings
Set your location for better personalized content and timezone display.
{form.formState.errors.personal_location && (

{form.formState.errors.personal_location.message}

)}

Your personal location (optional, displayed as text)

{form.formState.errors.home_park_id && (

{form.formState.errors.home_park_id.message}

)}

The theme park you visit most often or consider your "home" park

{form.formState.errors.timezone && (

{form.formState.errors.timezone.message}

)}

Used to display dates and times in your local timezone.

{form.formState.errors.preferred_language && (

{form.formState.errors.preferred_language.message}

)}
{/* Personal Information */}
Personal Information
Optional personal information that can be displayed on your profile.
{form.formState.errors.preferred_pronouns && (

{form.formState.errors.preferred_pronouns.message}

)}

How you'd like others to refer to you.

{/* Unit Preferences + Accessibility Options Grid */}
{/* Unit Preferences */}
Units & Measurements
Choose your preferred measurement system for displaying distances, speeds, and other measurements.

All measurements in the database are stored in metric and converted for display.

{/* Accessibility Options */}
Accessibility Options
Customize the interface to meet your accessibility needs.

Increase contrast for better visibility

updateAccessibility('high_contrast', checked)} />

Minimize animations and transitions

updateAccessibility('reduced_motion', checked)} />
{/* Save Button */}
); }