From 01837bc9994d74cddcccf0212aca546a5d758749 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:54:33 +0000 Subject: [PATCH] Refactor user settings implementation --- src/App.tsx | 2 + src/components/settings/AccountProfileTab.tsx | 316 ++++++++++++++ src/components/settings/DataExportTab.tsx | 290 +++++++++++++ src/components/settings/LocationTab.tsx | 386 ++++++++++++++++++ src/components/settings/NotificationsTab.tsx | 355 ++++++++++++++++ src/components/settings/PrivacyTab.tsx | 356 ++++++++++++++++ src/components/settings/SecurityTab.tsx | 309 ++++++++++++++ src/components/settings/SimplePhotoUpload.tsx | 81 ++++ src/integrations/supabase/types.ts | 105 +++++ src/pages/UserSettings.tsx | 131 ++++++ src/types/database.ts | 5 + ...5_ca8142fe-c339-45b0-b9d8-5079ded28396.sql | 120 ++++++ 12 files changed, 2456 insertions(+) create mode 100644 src/components/settings/AccountProfileTab.tsx create mode 100644 src/components/settings/DataExportTab.tsx create mode 100644 src/components/settings/LocationTab.tsx create mode 100644 src/components/settings/NotificationsTab.tsx create mode 100644 src/components/settings/PrivacyTab.tsx create mode 100644 src/components/settings/SecurityTab.tsx create mode 100644 src/components/settings/SimplePhotoUpload.tsx create mode 100644 src/pages/UserSettings.tsx create mode 100644 supabase/migrations/20250928194905_ca8142fe-c339-45b0-b9d8-5079ded28396.sql diff --git a/src/App.tsx b/src/App.tsx index 7ad40b5d..559d9ae0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import Rides from "./pages/Rides"; import Manufacturers from "./pages/Manufacturers"; import Auth from "./pages/Auth"; import Profile from "./pages/Profile"; +import UserSettings from "./pages/UserSettings"; import NotFound from "./pages/NotFound"; import Terms from "./pages/Terms"; import Privacy from "./pages/Privacy"; @@ -42,6 +43,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/settings/AccountProfileTab.tsx b/src/components/settings/AccountProfileTab.tsx new file mode 100644 index 00000000..60777fea --- /dev/null +++ b/src/components/settings/AccountProfileTab.tsx @@ -0,0 +1,316 @@ +import { useState } 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 { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; +import { useToast } from '@/hooks/use-toast'; +import { useAuth } from '@/hooks/useAuth'; +import { supabase } from '@/integrations/supabase/client'; +import { User, Upload, Trash2 } from 'lucide-react'; +import { SimplePhotoUpload } from './SimplePhotoUpload'; + +const profileSchema = z.object({ + username: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_-]+$/), + display_name: z.string().max(50).optional(), + bio: z.string().max(500).optional(), + preferred_pronouns: z.string().max(20).optional(), + show_pronouns: z.boolean(), + preferred_language: z.string() +}); + +type ProfileFormData = z.infer; + +export function AccountProfileTab() { + const { user, profile, refreshProfile } = useAuth(); + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + const [avatarLoading, setAvatarLoading] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + + const form = useForm({ + resolver: zodResolver(profileSchema), + defaultValues: { + username: profile?.username || '', + display_name: profile?.display_name || '', + bio: profile?.bio || '', + preferred_pronouns: profile?.preferred_pronouns || '', + show_pronouns: profile?.show_pronouns || false, + preferred_language: profile?.preferred_language || 'en' + } + }); + + const onSubmit = async (data: ProfileFormData) => { + if (!user) return; + + setLoading(true); + try { + const { error } = await supabase + .from('profiles') + .update({ + username: data.username, + display_name: data.display_name || null, + bio: data.bio || null, + preferred_pronouns: data.preferred_pronouns || null, + show_pronouns: data.show_pronouns, + preferred_language: data.preferred_language, + updated_at: new Date().toISOString() + }) + .eq('user_id', user.id); + + if (error) throw error; + + await refreshProfile(); + toast({ + title: 'Profile updated', + description: 'Your profile has been successfully updated.' + }); + } catch (error: any) { + toast({ + title: 'Error', + description: error.message || 'Failed to update profile', + variant: 'destructive' + }); + } finally { + setLoading(false); + } + }; + + const handleAvatarUpload = async (imageId: string, imageUrl: string) => { + if (!user) return; + + setAvatarLoading(true); + try { + const { error } = await supabase + .from('profiles') + .update({ + avatar_image_id: imageId, + avatar_url: imageUrl, + updated_at: new Date().toISOString() + }) + .eq('user_id', user.id); + + if (error) throw error; + + await refreshProfile(); + toast({ + title: 'Avatar updated', + description: 'Your avatar has been successfully updated.' + }); + } catch (error: any) { + toast({ + title: 'Error', + description: error.message || 'Failed to update avatar', + variant: 'destructive' + }); + } finally { + setAvatarLoading(false); + } + }; + + const handleDeleteAccount = async () => { + if (!user) return; + + try { + // This would typically involve multiple steps: + // 1. Anonymize or delete user data + // 2. Delete the auth user + // For now, we'll just show a message + toast({ + title: 'Account deletion requested', + description: 'Please contact support to complete account deletion.', + variant: 'destructive' + }); + } catch (error: any) { + toast({ + title: 'Error', + description: error.message || 'Failed to delete account', + variant: 'destructive' + }); + } + }; + + return ( +
+ {/* Profile Picture */} +
+

Profile Picture

+
+ + + + {profile?.display_name?.[0] || profile?.username?.[0] || } + + +
+ + + +

+ JPG, PNG or GIF. Max size 5MB. +

+
+
+
+ + + + {/* Profile Information */} +
+

Profile Information

+ +
+
+ + + {form.formState.errors.username && ( +

+ {form.formState.errors.username.message} +

+ )} +
+ +
+ + + {form.formState.errors.display_name && ( +

+ {form.formState.errors.display_name.message} +

+ )} +
+
+ +
+ +