Implement account deletion system

This commit is contained in:
gpt-engineer-app[bot]
2025-10-12 14:17:54 +00:00
parent 21ba87a664
commit 3a38b47108
10 changed files with 1429 additions and 50 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@@ -21,6 +21,8 @@ import { EmailChangeDialog } from './EmailChangeDialog';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { toast as sonnerToast } from 'sonner';
import { AccountDeletionDialog } from './AccountDeletionDialog';
import { DeletionStatusBanner } from './DeletionStatusBanner';
const profileSchema = z.object({
username: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_-]+$/),
@@ -44,6 +46,8 @@ export function AccountProfileTab() {
const [cancellingEmail, setCancellingEmail] = useState(false);
const [avatarUrl, setAvatarUrl] = useState<string>(profile?.avatar_url || '');
const [avatarImageId, setAvatarImageId] = useState<string>(profile?.avatar_image_id || '');
const [showDeletionDialog, setShowDeletionDialog] = useState(false);
const [deletionRequest, setDeletionRequest] = useState<any>(null);
const form = useForm<ProfileFormData>({
resolver: zodResolver(profileSchema),
@@ -57,6 +61,26 @@ export function AccountProfileTab() {
}
});
// Check for existing deletion request on mount
useEffect(() => {
const checkDeletionRequest = async () => {
if (!user?.id) return;
const { data, error } = await supabase
.from('account_deletion_requests')
.select('*')
.eq('user_id', user.id)
.eq('status', 'pending')
.maybeSingle();
if (!error && data) {
setDeletionRequest(data);
}
};
checkDeletionRequest();
}, [user?.id]);
const onSubmit = async (data: ProfileFormData) => {
if (!user) return;
@@ -200,30 +224,36 @@ export function AccountProfileTab() {
}
};
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'
});
const handleDeletionRequested = async () => {
// Refresh deletion request data
const { data, error } = await supabase
.from('account_deletion_requests')
.select('*')
.eq('user_id', user!.id)
.eq('status', 'pending')
.maybeSingle();
if (!error && data) {
setDeletionRequest(data);
}
};
const handleDeletionCancelled = () => {
setDeletionRequest(null);
};
const isDeactivated = (profile as any)?.deactivated || false;
return (
<div className="space-y-8">
{/* Deletion Status Banner */}
{deletionRequest && (
<DeletionStatusBanner
scheduledDate={deletionRequest.scheduled_deletion_at}
onCancelled={handleDeletionCancelled}
/>
)}
{/* Profile Picture */}
<div className="space-y-4">
<h3 className="text-lg font-medium">Profile Picture</h3>
@@ -257,6 +287,7 @@ export function AccountProfileTab() {
id="username"
{...form.register('username')}
placeholder="Enter your username"
disabled={isDeactivated}
/>
{form.formState.errors.username && (
<p className="text-sm text-destructive">
@@ -325,9 +356,14 @@ export function AccountProfileTab() {
</div>
</div>
<Button type="submit" disabled={loading}>
<Button type="submit" disabled={loading || isDeactivated}>
{loading ? 'Saving...' : 'Save Changes'}
</Button>
{isDeactivated && (
<p className="text-sm text-muted-foreground">
Your account is deactivated. Profile editing is disabled.
</p>
)}
</form>
<Separator />
@@ -440,39 +476,36 @@ export function AccountProfileTab() {
<CardHeader>
<CardTitle className="text-destructive">Danger Zone</CardTitle>
<CardDescription>
These actions cannot be undone. Please be careful.
Permanently delete your account with a 2-week waiting period
</CardDescription>
</CardHeader>
<CardContent>
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogTrigger asChild>
<Button variant="destructive" className="w-fit">
<Trash2 className="w-4 h-4 mr-2" />
Delete Account
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account
and remove all your data from our servers, including your reviews,
ride credits, and profile information.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteAccount}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Yes, delete my account
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium">Delete Account</p>
<p className="text-sm text-muted-foreground">
Your profile and reviews will be deleted, but your contributions (submissions, photos) will be preserved
</p>
</div>
<Button
variant="destructive"
onClick={() => setShowDeletionDialog(true)}
disabled={!!deletionRequest}
>
<Trash2 className="w-4 h-4 mr-2" />
{deletionRequest ? 'Deletion Pending' : 'Delete Account'}
</Button>
</div>
</CardContent>
</Card>
{/* Account Deletion Dialog */}
<AccountDeletionDialog
open={showDeletionDialog}
onOpenChange={setShowDeletionDialog}
userEmail={user?.email || ''}
onDeletionRequested={handleDeletionRequested}
/>
</div>
);
}