LibreChat/client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx
Marco Beretta a5189052ec
️ fix: Accessibility, UI consistency, dialog & avatar refactors (#9975)
* 🔧 refactor: Improve accessibility and styling in ChatGroupItem and FilterPrompts components

* 🔧 fix: Add button type and keyboard accessibility to dropdown menu trigger in ChatGroupItem

* 🔧 fix(757): Enhance accessibility by updating aria-labels and adding localization for prompt groups

* 🔧 fix(618): Update version to 0.3.1 and enhance accessibility in InfoHoverCard component

* 🔧 fix(618): Update aria-label in InfoHoverCard to use dynamic text prop for improved accessibility

* 🔧 fix: Enhance accessibility by updating aria-labels and roles in Conversations components

* 🔧 fix(620): Enhance accessibility by adding tabIndex to Tabs.Content components in ArtifactTabs, Settings, and Speech components

* refactor: remove RevokeKeysButton component and update related components for accessibility

- Deleted RevokeKeysButton component.
- Updated SharedLinks and General components to use Label for accessibility.
- Enhanced Personalization component with aria-labelledby and aria-describedby attributes.
- Refactored ConversationModeSwitch to use ToggleSwitch for better state management.
- Improved AutoSendTextSelector with local state management and accessibility attributes.
- Replaced Switch components with ToggleSwitch in various Speech and TTS components for consistency.
- Added aria-labelledby attributes to Dropdown components for better accessibility.
- Updated translation.json to include new localization keys and improved existing ones.
- Enhanced Slider component to support aria attributes for better accessibility.

* 🔧 fix: Enhance user feedback for API key operations with success and error messages

* 🔧 fix: Update aria-labels in Avatar component for improved localization and accessibility

* 🔧 fix: Refactor handleFile and handleDrop functions for improved readability and maintainability
2025-10-07 14:12:49 -04:00

140 lines
4.2 KiB
TypeScript

import { LockIcon, Trash } from 'lucide-react';
import React, { useState, useCallback } from 'react';
import {
Label,
Input,
Button,
Spinner,
OGDialog,
OGDialogContent,
OGDialogTrigger,
OGDialogHeader,
OGDialogTitle,
} from '@librechat/client';
import { useDeleteUserMutation } from '~/data-provider';
import { useAuthContext } from '~/hooks/AuthContext';
import { LocalizeFunction } from '~/common';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolean }) => {
const localize = useLocalize();
const { user, logout } = useAuthContext();
const { mutate: deleteUser, isLoading: isDeleting } = useDeleteUserMutation({
onMutate: () => logout(),
});
const [isDialogOpen, setDialogOpen] = useState<boolean>(false);
const [isLocked, setIsLocked] = useState(true);
const handleDeleteUser = () => {
if (!isLocked) {
deleteUser(undefined);
}
};
const handleInputChange = useCallback(
(newEmailInput: string) => {
const isEmailCorrect =
newEmailInput.trim().toLowerCase() === user?.email.trim().toLowerCase();
setIsLocked(!isEmailCorrect);
},
[user?.email],
);
return (
<>
<OGDialog open={isDialogOpen} onOpenChange={setDialogOpen}>
<div className="flex items-center justify-between">
<Label id="delete-account-label">{localize('com_nav_delete_account')}</Label>
<OGDialogTrigger asChild>
<Button
aria-labelledby="delete-account-label"
variant="destructive"
onClick={() => setDialogOpen(true)}
disabled={disabled}
>
{localize('com_ui_delete')}
</Button>
</OGDialogTrigger>
</div>
<OGDialogContent className="w-11/12 max-w-md">
<OGDialogHeader>
<OGDialogTitle className="text-lg font-medium leading-6">
{localize('com_nav_delete_account_confirm')}
</OGDialogTitle>
</OGDialogHeader>
<div className="mb-8 text-sm text-black dark:text-white">
<ul className="font-semibold text-amber-600">
<li>{localize('com_nav_delete_warning')}</li>
<li>{localize('com_nav_delete_data_info')}</li>
</ul>
</div>
<div className="flex-col items-center justify-center">
<div className="mb-4">
{renderInput(
localize('com_nav_delete_account_email_placeholder'),
'email-confirm-input',
user?.email ?? '',
(e) => handleInputChange(e.target.value),
)}
</div>
{renderDeleteButton(handleDeleteUser, isDeleting, isLocked, localize)}
</div>
</OGDialogContent>
</OGDialog>
</>
);
};
const renderInput = (
label: string,
id: string,
value: string,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
) => (
<div className="mb-4">
<label className="mb-1 block text-sm font-medium text-black dark:text-white" htmlFor={id}>
{label}
</label>
<Input id={id} onChange={onChange} placeholder={value} />
</div>
);
const renderDeleteButton = (
handleDeleteUser: () => void,
isDeleting: boolean,
isLocked: boolean,
localize: LocalizeFunction,
) => (
<button
className={cn(
'mt-4 flex w-full items-center justify-center rounded-lg bg-surface-tertiary px-4 py-2 transition-all duration-200',
isLocked ? 'cursor-not-allowed opacity-30' : 'bg-destructive text-destructive-foreground',
)}
onClick={handleDeleteUser}
disabled={isDeleting || isLocked}
>
{isDeleting ? (
<div className="flex h-6 justify-center">
<Spinner className="icon-sm m-auto" />
</div>
) : (
<>
{isLocked ? (
<>
<LockIcon className="size-5" />
<span className="ml-2">{localize('com_ui_locked')}</span>
</>
) : (
<>
<Trash className="size-5" />
<span className="ml-2">{localize('com_nav_delete_account_button')}</span>
</>
)}
</>
)}
</button>
);
export default DeleteAccount;