fix: add account deletion feature and enhance localization messages

- Introduced `DeleteAccountCard` component for users to permanently delete their accounts, including a confirmation dialog to prevent accidental deletions.
- Updated localization files in English and Chinese to include new messages related to account deletion, enhancing user guidance and clarity.
- Improved layout of the account settings page to incorporate the new delete account functionality alongside existing update features.
This commit is contained in:
javayhu 2025-03-16 01:15:01 +08:00
parent d51a22e030
commit aef4ce6296
6 changed files with 170 additions and 7 deletions

View File

@ -328,8 +328,21 @@
"success": "Password updated successfully",
"fail": "Failed to update password"
},
"deleteAccount": {
"title": "Delete Account",
"description": "Permanently remove your account and all of its contents",
"warning": "This action is not reversible, so please continue with caution",
"button": "Delete Account",
"confirmTitle": "Delete Account",
"confirmDescription": "Are you sure you want to delete your account? This action cannot be undone.",
"confirm": "Delete",
"deleting": "Deleting...",
"success": "Account deleted successfully",
"fail": "Failed to delete account"
},
"save": "Save",
"saving": "Saving..."
"saving": "Saving...",
"cancel": "Cancel"
},
"billing": {
"title": "Billing"

View File

@ -323,6 +323,18 @@
"success": "密码更新成功",
"fail": "更新密码失败"
},
"deleteAccount": {
"title": "删除账号",
"description": "永久删除您的账号和所有内容",
"warning": "此操作是不可逆的,请谨慎操作",
"button": "删除账号",
"confirmTitle": "删除账号",
"confirmDescription": "您确定要删除您的账号吗?此操作无法撤销",
"confirm": "删除",
"deleting": "删除中...",
"success": "账号删除成功",
"fail": "删除账号失败"
},
"save": "保存",
"saving": "保存中..."
},

View File

@ -2,6 +2,7 @@ import { DashboardHeader } from '@/components/dashboard/dashboard-header';
import { UpdateAvatarCard } from '@/components/settings/account/update-avatar-card';
import { UpdateNameCard } from '@/components/settings/account/update-name-card';
import { UpdatePasswordCard } from '@/components/settings/account/update-password-card';
import { DeleteAccountCard } from '@/components/settings/account/delete-account-card';
import { useTranslations } from 'next-intl';
export default function SettingsAccountPage() {
@ -21,10 +22,16 @@ export default function SettingsAccountPage() {
return (
<>
<DashboardHeader breadcrumbs={breadcrumbs} />
<div className="flex flex-1 flex-col gap-4 p-4 pl-12">
<UpdateAvatarCard />
<UpdateNameCard />
<UpdatePasswordCard />
<div className="p-8">
<div className="grid grid-cols-1 md:grid-cols-1 gap-6 max-w-6xl">
<div className="space-y-6">
<UpdateAvatarCard />
<UpdateNameCard />
<UpdatePasswordCard />
<DeleteAccountCard />
</div>
</div>
</div>
</>
);

View File

@ -23,7 +23,8 @@ interface DashboardHeaderProps {
export function DashboardHeader({ breadcrumbs, actions }: DashboardHeaderProps) {
return (
<header className="flex h-16 border-b shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<header className="flex h-16 border-b shrink-0 items-center gap-2 sticky top-0 z-10 bg-background
transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" />

View File

@ -0,0 +1,130 @@
'use client';
import { FormError } from '@/components/shared/form-error';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle
} from '@/components/ui/card';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog';
import { useLocaleRouter } from '@/i18n/navigation';
import { authClient } from '@/lib/auth-client';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import { toast } from 'sonner';
/**
* Delete user account
*
* This component allows users to permanently delete their account.
* It includes a confirmation dialog to prevent accidental deletions.
*/
export function DeleteAccountCard() {
const t = useTranslations('Dashboard.sidebar.settings.items.account');
const [isDeleting, setIsDeleting] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false);
const [error, setError] = useState<string | undefined>('');
const { data: session, refetch } = authClient.useSession();
const router = useLocaleRouter();
// Check if user exists
const user = session?.user;
if (!user) {
return null;
}
// Handle account deletion
const handleDeleteAccount = async () => {
setIsDeleting(true);
await authClient.deleteUser(
{},
{
onRequest: () => {
setIsDeleting(true);
setError('');
},
onResponse: () => {
setIsDeleting(false);
},
onSuccess: () => {
toast.success(t('deleteAccount.success'));
refetch();
router.push('/');
},
onError: (ctx) => {
console.error('delete account error:', ctx.error);
// { "message": "Session expired. Re-authenticate to perform this action.", "code": "SESSION_EXPIRED_REAUTHENTICATE_TO_PERFORM_THIS_ACTION", "status": 400, "statusText": "BAD_REQUEST" }
setError(`${ctx.error.status}: ${ctx.error.message}`);
toast.error(t('deleteAccount.fail'));
},
}
);
};
return (
<Card className="max-w-md md:max-w-lg border-destructive/50">
<CardHeader>
<CardTitle className="text-lg font-bold text-destructive">{t('deleteAccount.title')}</CardTitle>
<CardDescription>
{t('deleteAccount.description')}
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{t('deleteAccount.warning')}
</p>
<div className="mt-4">
<FormError message={error} />
</div>
</CardContent>
<CardFooter className="px-6 py-4 flex justify-end items-center bg-muted">
<Button
variant="destructive"
onClick={() => setShowConfirmation(true)}
>
{t('deleteAccount.button')}
</Button>
</CardFooter>
{/* Confirmation Dialog */}
<Dialog open={showConfirmation} onOpenChange={setShowConfirmation}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-destructive">{t('deleteAccount.confirmTitle')}</DialogTitle>
<DialogDescription>
{t('deleteAccount.confirmDescription')}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setShowConfirmation(false)}
>
{t('cancel')}
</Button>
<Button
variant="destructive"
onClick={handleDeleteAccount}
disabled={isDeleting}
>
{isDeleting ? t('deleteAccount.deleting') : t('deleteAccount.confirm')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card>
);
}

View File

@ -31,7 +31,7 @@ import { z } from 'zod';
*
* NOTICE: we update username instead of name in user table
*
* TODO: by default, username is empty, how can we show the username in user-button?
* TODO: by default, username is empty, how can we show the username after signup?
*
* https://www.better-auth.com/docs/plugins/username
*/