feat: delete users - not tested

This commit is contained in:
Eric Gullickson
2025-12-22 18:20:25 -06:00
parent 91b4534e76
commit 4897f0a52c
73 changed files with 4923 additions and 62 deletions

View File

@@ -0,0 +1,116 @@
/**
* @ai-summary Mobile modal for requesting account deletion with 30-day grace period
*/
import React, { useState, useEffect } from 'react';
import { useRequestDeletion } from '../hooks/useDeletion';
interface DeleteAccountModalProps {
isOpen: boolean;
onClose: () => void;
}
export const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({ isOpen, onClose }) => {
const [password, setPassword] = useState('');
const [confirmationText, setConfirmationText] = useState('');
const requestDeletionMutation = useRequestDeletion();
// Clear form when modal closes
useEffect(() => {
if (!isOpen) {
setPassword('');
setConfirmationText('');
}
}, [isOpen]);
if (!isOpen) return null;
const handleSubmit = async () => {
if (!password || confirmationText !== 'DELETE') {
return;
}
await requestDeletionMutation.mutateAsync({ password, confirmationText });
onClose();
};
const isValid = password.length > 0 && confirmationText === 'DELETE';
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-lg p-6 max-w-md w-full">
<h3 className="text-xl font-semibold text-slate-800 mb-4">Delete Account</h3>
{/* Warning Alert */}
<div className="mb-4 p-4 bg-amber-50 border border-amber-200 rounded-lg">
<p className="font-semibold text-amber-900 mb-2">30-Day Grace Period</p>
<p className="text-sm text-amber-800">
Your account will be scheduled for deletion in 30 days. You can cancel this request at any time during
the grace period by logging back in.
</p>
</div>
{/* Password Input */}
<div className="mb-4">
<label className="block text-sm font-medium text-slate-700 mb-1">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500"
style={{ fontSize: '16px', minHeight: '44px' }}
autoComplete="current-password"
/>
<p className="text-xs text-slate-500 mt-1">Enter your password to confirm</p>
</div>
{/* Confirmation Input */}
<div className="mb-6">
<label className="block text-sm font-medium text-slate-700 mb-1">
Type DELETE to confirm
</label>
<input
type="text"
value={confirmationText}
onChange={(e) => setConfirmationText(e.target.value)}
placeholder="DELETE"
className={`w-full px-3 py-2 border rounded-lg focus:ring-2 ${
confirmationText.length > 0 && confirmationText !== 'DELETE'
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
: 'border-slate-300 focus:ring-red-500 focus:border-red-500'
}`}
style={{ fontSize: '16px', minHeight: '44px' }}
/>
<p className="text-xs text-slate-500 mt-1">Type the word "DELETE" (all caps) to confirm</p>
</div>
{/* Action Buttons */}
<div className="flex space-x-3">
<button
onClick={onClose}
disabled={requestDeletionMutation.isPending}
className="flex-1 py-2.5 px-4 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300 transition-colors disabled:opacity-50"
style={{ minHeight: '44px' }}
>
Cancel
</button>
<button
onClick={handleSubmit}
disabled={!isValid || requestDeletionMutation.isPending}
className="flex-1 py-2.5 px-4 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors disabled:opacity-50 flex items-center justify-center"
style={{ minHeight: '44px' }}
>
{requestDeletionMutation.isPending ? (
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
) : (
'Delete Account'
)}
</button>
</div>
</div>
</div>
);
};

View File

@@ -6,6 +6,8 @@ import { useSettings } from '../hooks/useSettings';
import { useProfile, useUpdateProfile } from '../hooks/useProfile';
import { useAdminAccess } from '../../../core/auth/useAdminAccess';
import { useNavigationStore } from '../../../core/store';
import { DeleteAccountModal } from './DeleteAccountModal';
import { PendingDeletionBanner } from './PendingDeletionBanner';
interface ToggleSwitchProps {
enabled: boolean;
@@ -105,11 +107,6 @@ export const MobileSettingsScreen: React.FC = () => {
setShowDataExport(false);
};
const handleDeleteAccount = () => {
// TODO: Implement account deletion
console.log('Deleting account...');
setShowDeleteConfirm(false);
};
const handleEditProfile = () => {
setIsEditingProfile(true);
@@ -190,6 +187,9 @@ export const MobileSettingsScreen: React.FC = () => {
<p className="text-slate-500 mt-2">Manage your account and preferences</p>
</div>
{/* Pending Deletion Banner */}
<PendingDeletionBanner />
{/* Profile Section */}
<GlassCard padding="md">
<div>
@@ -488,30 +488,11 @@ export const MobileSettingsScreen: React.FC = () => {
</div>
</Modal>
{/* Delete Account Confirmation */}
<Modal
{/* Delete Account Modal */}
<DeleteAccountModal
isOpen={showDeleteConfirm}
onClose={() => setShowDeleteConfirm(false)}
title="Delete Account"
>
<p className="text-slate-600 mb-4">
This action cannot be undone. All your data will be permanently deleted.
</p>
<div className="flex space-x-3">
<button
onClick={() => setShowDeleteConfirm(false)}
className="flex-1 py-2 px-4 bg-gray-200 text-gray-700 rounded-lg font-medium"
>
Cancel
</button>
<button
onClick={handleDeleteAccount}
className="flex-1 py-2 px-4 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors"
>
Delete
</button>
</div>
</Modal>
/>
</div>
</MobileContainer>
);

View File

@@ -0,0 +1,53 @@
/**
* @ai-summary Mobile banner showing pending account deletion with cancel option
*/
import React from 'react';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { useDeletionStatus, useCancelDeletion } from '../hooks/useDeletion';
export const PendingDeletionBanner: React.FC = () => {
const { data: deletionStatus, isLoading } = useDeletionStatus();
const cancelDeletionMutation = useCancelDeletion();
// Don't show banner if not loading and not pending deletion
if (isLoading || !deletionStatus?.isPendingDeletion) {
return null;
}
const handleCancelDeletion = async () => {
await cancelDeletionMutation.mutateAsync();
};
return (
<GlassCard padding="md" className="bg-amber-50/80 border-amber-200/70">
<div className="space-y-3">
<div>
<h3 className="text-lg font-semibold text-amber-900 mb-1">Account Deletion Pending</h3>
<p className="text-sm text-amber-800">
Your account is scheduled for deletion in{' '}
<strong>{deletionStatus.daysRemaining} {deletionStatus.daysRemaining === 1 ? 'day' : 'days'}</strong>.
</p>
{deletionStatus.deletionScheduledFor && (
<p className="text-xs text-amber-700 mt-1">
Scheduled for: {new Date(deletionStatus.deletionScheduledFor).toLocaleDateString()}
</p>
)}
</div>
<button
onClick={handleCancelDeletion}
disabled={cancelDeletionMutation.isPending}
className="w-full py-2.5 px-4 bg-amber-600 text-white rounded-lg font-medium hover:bg-amber-700 transition-colors disabled:opacity-50 flex items-center justify-center"
style={{ minHeight: '44px' }}
>
{cancelDeletionMutation.isPending ? (
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
) : (
'Cancel Deletion'
)}
</button>
</div>
</GlassCard>
);
};