Files
thrilltrack-explorer/src/components/settings/LocationTab.tsx
gpt-engineer-app[bot] 98b06cdf6e Refactor settings page
2025-09-28 20:58:08 +00:00

302 lines
11 KiB
TypeScript

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 } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch';
import { useToast } from '@/hooks/use-toast';
import { useAuth } from '@/hooks/useAuth';
import { supabase } from '@/integrations/supabase/client';
import { MapPin, Calendar, Globe, Accessibility } from 'lucide-react';
const locationSchema = z.object({
preferred_pronouns: z.string().max(20).optional(),
timezone: z.string(),
preferred_language: z.string(),
personal_location: z.string().max(100).optional(),
home_park_id: z.string().optional()
});
type LocationFormData = z.infer<typeof locationSchema>;
interface AccessibilityOptions {
font_size: 'small' | 'medium' | 'large';
high_contrast: boolean;
reduced_motion: boolean;
}
export function LocationTab() {
const {
user,
profile,
refreshProfile
} = useAuth();
const {
toast
} = useToast();
const [loading, setLoading] = useState(false);
const [parks, setParks] = useState<any[]>([]);
const [accessibility, setAccessibility] = useState<AccessibilityOptions>({
font_size: 'medium',
high_contrast: false,
reduced_motion: false
});
const form = useForm<LocationFormData>({
resolver: zodResolver(locationSchema),
defaultValues: {
preferred_pronouns: profile?.preferred_pronouns || '',
timezone: profile?.timezone || 'UTC',
preferred_language: profile?.preferred_language || 'en',
personal_location: (profile as any)?.personal_location || '',
home_park_id: (profile as any)?.home_park_id || ''
}
});
useEffect(() => {
fetchParks();
fetchAccessibilityPreferences();
}, [user]);
const fetchParks = async () => {
try {
const { data, error } = await supabase
.from('parks')
.select('id, name, location_id, locations(city, state_province, country)')
.order('name');
if (error) throw error;
setParks(data || []);
} catch (error) {
console.error('Error fetching parks:', error);
}
};
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') {
console.error('Error fetching accessibility preferences:', error);
return;
}
if (data?.accessibility_options) {
setAccessibility(data.accessibility_options as any);
}
} catch (error) {
console.error('Error fetching accessibility preferences:', error);
}
};
const onSubmit = async (data: LocationFormData) => {
if (!user) return;
setLoading(true);
try {
// Save profile information
const { error: profileError } = await supabase.from('profiles').update({
preferred_pronouns: data.preferred_pronouns || null,
timezone: data.timezone,
preferred_language: data.preferred_language,
personal_location: data.personal_location || null,
home_park_id: data.home_park_id || null,
updated_at: new Date().toISOString()
}).eq('user_id', user.id);
if (profileError) throw profileError;
// Save accessibility preferences
const { error: accessibilityError } = await supabase.from('user_preferences').upsert([{
user_id: user.id,
accessibility_options: accessibility as any,
updated_at: new Date().toISOString()
}]);
if (accessibilityError) throw accessibilityError;
await refreshProfile();
toast({
title: 'Settings saved',
description: 'Your location, personal information, and accessibility settings have been updated.'
});
} catch (error: any) {
toast({
title: 'Error',
description: error.message || 'Failed to save settings',
variant: 'destructive'
});
} finally {
setLoading(false);
}
};
const updateAccessibility = (key: keyof AccessibilityOptions, value: any) => {
setAccessibility(prev => ({
...prev,
[key]: value
}));
};
const timezones = ['UTC', 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Toronto', 'Europe/London', 'Europe/Berlin', 'Europe/Paris', 'Asia/Tokyo', 'Asia/Shanghai', 'Australia/Sydney'];
return <div className="space-y-8">
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
{/* Location Settings */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<MapPin className="w-5 h-5" />
<h3 className="text-lg font-medium">Location Settings</h3>
</div>
<Card>
<CardHeader>
<CardDescription>
Set your location for better personalized content and timezone display.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="personal_location">Your Location</Label>
<Input
id="personal_location"
{...form.register('personal_location')}
placeholder="e.g., San Francisco, CA or Berlin, Germany"
/>
<p className="text-sm text-muted-foreground">
Your personal location (optional, displayed as text)
</p>
</div>
<div className="space-y-2">
<Label htmlFor="home_park_id">Home Park</Label>
<Select value={form.watch('home_park_id')} onValueChange={value => form.setValue('home_park_id', value)}>
<SelectTrigger>
<SelectValue placeholder="Select your home park" />
</SelectTrigger>
<SelectContent>
{parks.map(park => (
<SelectItem key={park.id} value={park.id}>
{park.name}
{park.locations && (
<>
{park.locations.city && `, ${park.locations.city}`}
{park.locations.state_province && `, ${park.locations.state_province}`}
{park.locations.country && `, ${park.locations.country}`}
</>
)}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
The theme park you visit most often or consider your "home" park
</p>
</div>
<div className="space-y-2">
<Label htmlFor="timezone">Timezone</Label>
<Select value={form.watch('timezone')} onValueChange={value => form.setValue('timezone', value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{timezones.map(tz => <SelectItem key={tz} value={tz}>
{tz}
</SelectItem>)}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
Used to display dates and times in your local timezone.
</p>
</div>
</CardContent>
</Card>
</div>
<Separator />
{/* Personal Information */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<Calendar className="w-5 h-5" />
<h3 className="text-lg font-medium">Personal Information</h3>
</div>
<Card>
<CardHeader>
<CardDescription>
Optional personal information that can be displayed on your profile.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="preferred_pronouns">Preferred Pronouns</Label>
<Input id="preferred_pronouns" {...form.register('preferred_pronouns')} placeholder="e.g., they/them, she/her, he/him" />
<p className="text-sm text-muted-foreground">
How you'd like others to refer to you.
</p>
</div>
</CardContent>
</Card>
</div>
<Separator />
{/* Accessibility Options */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<Accessibility className="w-5 h-5" />
<h3 className="text-lg font-medium">Accessibility Options</h3>
</div>
<Card>
<CardHeader>
<CardDescription>
Customize the interface to meet your accessibility needs.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-3">
<Label>Font Size</Label>
<Select value={accessibility.font_size} onValueChange={(value: 'small' | 'medium' | 'large') => updateAccessibility('font_size', value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="small">Small</SelectItem>
<SelectItem value="medium">Medium (Default)</SelectItem>
<SelectItem value="large">Large</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label>High Contrast</Label>
<p className="text-sm text-muted-foreground">
Increase contrast for better visibility
</p>
</div>
<Switch checked={accessibility.high_contrast} onCheckedChange={checked => updateAccessibility('high_contrast', checked)} />
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label>Reduced Motion</Label>
<p className="text-sm text-muted-foreground">
Minimize animations and transitions
</p>
</div>
<Switch checked={accessibility.reduced_motion} onCheckedChange={checked => updateAccessibility('reduced_motion', checked)} />
</div>
</CardContent>
</Card>
</div>
<div className="flex justify-end">
<Button type="submit" disabled={loading}>
{loading ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</form>
</div>;
}