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, Mail, AlertCircle, X } from 'lucide-react'; import { PhotoUpload } from '@/components/upload/PhotoUpload'; import { notificationService } from '@/lib/notificationService'; import { EmailChangeDialog } from './EmailChangeDialog'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { toast as sonnerToast } from 'sonner'; 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, pendingEmail } = useAuth(); const { toast } = useToast(); const [loading, setLoading] = useState(false); const [avatarLoading, setAvatarLoading] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showEmailDialog, setShowEmailDialog] = useState(false); const [showCancelEmailDialog, setShowCancelEmailDialog] = useState(false); const [cancellingEmail, setCancellingEmail] = useState(false); const [avatarUrl, setAvatarUrl] = useState(profile?.avatar_url || ''); const [avatarImageId, setAvatarImageId] = useState(profile?.avatar_image_id || ''); 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 usernameChanged = profile?.username !== data.username; 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; // Update Novu subscriber if username changed if (usernameChanged && notificationService.isEnabled()) { await notificationService.updateSubscriber({ subscriberId: user.id, email: user.email, firstName: data.username, // Send username as firstName to Novu }); } 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 (urls: string[], imageId?: string) => { if (!user || !urls[0]) return; const newAvatarUrl = urls[0]; const newImageId = imageId || ''; // Update local state immediately setAvatarUrl(newAvatarUrl); setAvatarImageId(newImageId); try { const { error } = await supabase .from('profiles') .update({ avatar_url: newAvatarUrl, avatar_image_id: newImageId, 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) { // Revert local state on error setAvatarUrl(profile?.avatar_url || ''); setAvatarImageId(profile?.avatar_image_id || ''); toast({ title: 'Error', description: error.message || 'Failed to update avatar', variant: 'destructive' }); } }; const handleCancelEmailChange = async () => { if (!user?.email || !pendingEmail) return; setCancellingEmail(true); try { // Reset email to current email (effectively cancels the pending change) const { error: updateError } = await supabase.auth.updateUser({ email: user.email }); if (updateError) throw updateError; // Update Novu subscriber back to current email if (notificationService.isEnabled()) { await notificationService.updateSubscriber({ subscriberId: user.id, email: user.email, firstName: profile?.username || user.email.split('@')[0], }); } // Log the cancellation in audit log await supabase.from('admin_audit_log').insert({ admin_user_id: user.id, target_user_id: user.id, action: 'email_change_cancelled', details: { cancelled_email: pendingEmail, current_email: user.email, timestamp: new Date().toISOString() } }); sonnerToast.success('Email change cancelled', { description: 'Your email change request has been cancelled.' }); setShowCancelEmailDialog(false); await refreshProfile(); } catch (error: any) { sonnerToast.error('Failed to cancel email change', { description: error.message || 'An error occurred while cancelling the email change.' }); } finally { setCancellingEmail(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

{ toast({ title: "Upload Error", description: error, variant: "destructive" }); }} />
{/* Profile Information */}

Profile Information

{form.formState.errors.username && (

{form.formState.errors.username.message}

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

{form.formState.errors.display_name.message}

)}