LibreChat/client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx

171 lines
5.4 KiB
TypeScript
Raw Normal View History

import React, { useState, useCallback } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, Input } from '~/components/ui';
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
import { useDeleteUserMutation } from '~/data-provider';
import { Spinner, LockIcon } from '~/components/svg';
import { useAuthContext } from '~/hooks/AuthContext';
import { useLocalize } from '~/hooks';
import DangerButton from '../DangerButton';
const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolean }) => {
const localize = useLocalize();
const { user, logout } = useAuthContext();
const { mutate: deleteUser, isLoading: isDeleting } = useDeleteUserMutation({
📧 feat: email verification (#2344) * feat: verification email * chore: email verification invalid; localize: update * fix: redirect to login when signup: fix: save emailVerified correctly * docs: update ALLOW_UNVERIFIED_EMAIL_LOGIN; fix: don't accept login only when ALLOW_UNVERIFIED_EMAIL_LOGIN = true * fix: user needs to be authenticated * style: update * fix: registration success message and redirect logic * refactor: use `isEnabled` in ALLOW_UNVERIFIED_EMAIL_LOGIN * refactor: move checkEmailConfig to server/utils * refactor: use req as param for verifyEmail function * chore: jsdoc * chore: remove console log * refactor: rename `createNewUser` to `createSocialUser` * refactor: update typing and add expiresAt field to userSchema * refactor: begin use of user methods over direct model access for User * refactor: initial email verification rewrite * chore: typing * refactor: registration flow rewrite * chore: remove help center text * refactor: update getUser to getUserById and add findUser methods. general fixes from recent changes * refactor: Update updateUser method to remove expiresAt field and use $set and $unset operations, createUser now returns Id only * refactor: Update openidStrategy to use optional chaining for avatar check, move saveBuffer init to buffer condition * refactor: logout on deleteUser mutatation * refactor: Update openidStrategy login success message format * refactor: Add emailVerified field to Discord and Facebook profile details * refactor: move limiters to separate middleware dir * refactor: Add limiters for email verification and password reset * refactor: Remove getUserController and update routes and controllers accordingly * refactor: Update getUserById method to exclude password and version fields * refactor: move verification to user route, add resend verification option * refactor: Improve email verification process and resend option * refactor: remove more direct model access of User and remove unused code * refactor: replace user authentication methods and token generation * fix: add user.id to jwt user * refactor: Update AuthContext to include setError function, add resend link to Login Form, make registration redirect shorter * fix(updateUserPluginsService): ensure userPlugins variable is defined * refactor: Delete all shared links for a specific user * fix: remove use of direct User.save() in handleExistingUser * fix(importLibreChatConvo): handle missing createdAt field in messages --------- Co-authored-by: Danny Avila <danny@librechat.ai>
2024-06-07 21:06:47 +02:00
onMutate: () => logout(),
});
const [isDialogOpen, setDialogOpen] = useState<boolean>(false);
const [deleteInput, setDeleteInput] = useState('');
const [emailInput, setEmailInput] = useState('');
const [isLocked, setIsLocked] = useState(true);
const onClick = useCallback(() => {
setDialogOpen(true);
}, []);
const handleDeleteUser = () => {
if (!isLocked) {
deleteUser(undefined);
}
};
const handleInputChange = useCallback(
(newEmailInput: string, newDeleteInput: string) => {
const isEmailCorrect =
newEmailInput.trim().toLowerCase() === user?.email?.trim().toLowerCase();
const isDeleteInputCorrect = newDeleteInput === 'DELETE';
setIsLocked(!(isEmailCorrect && isDeleteInputCorrect));
},
[user?.email],
);
return (
<>
<div className="flex items-center justify-between">
<span>{localize('com_nav_delete_account')}</span>
<label>
<DangerButton
id={'delete-user-account'}
disabled={disabled}
onClick={onClick}
actionTextCode="com_ui_delete"
className={cn(
'btn relative border-none bg-red-500 text-white hover:bg-red-700 dark:hover:bg-red-700',
)}
confirmClear={false}
infoTextCode={''}
dataTestIdInitial={''}
dataTestIdConfirm={''}
/>
</label>
</div>
<Dialog open={isDialogOpen} onOpenChange={() => setDialogOpen(false)}>
<DialogContent
className={cn('shadow-2xl md:h-[500px] md:w-[450px]')}
style={{ borderRadius: '12px', padding: '20px' }}
>
<DialogHeader>
<DialogTitle className="text-lg font-medium leading-6">
{localize('com_nav_delete_account_confirm')}
</DialogTitle>
</DialogHeader>
<div className="mb-20 text-sm text-black dark:text-white">
<ul>
<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) => {
setEmailInput(e.target.value);
handleInputChange(e.target.value, deleteInput);
},
)}
</div>
<div className="mb-4">
{renderInput(
localize('com_nav_delete_account_confirm_placeholder'),
'delete-confirm-input',
'',
(e) => {
setDeleteInput(e.target.value);
handleInputChange(emailInput, e.target.value);
},
)}
</div>
{renderDeleteButton(handleDeleteUser, isDeleting, isLocked, localize)}
</div>
</DialogContent>
</Dialog>
</>
);
};
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">{label}</label>
<Input
id={id}
onChange={onChange}
placeholder={value}
className={cn(
defaultTextProps,
'h-10 max-h-10 w-full max-w-full rounded-md bg-white px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
);
const renderDeleteButton = (
handleDeleteUser: () => void,
isDeleting: boolean,
isLocked: boolean,
localize: (key: string) => string,
) => (
<button
className={cn(
'mt-4 flex w-full items-center justify-center rounded-lg px-4 py-2 transition-colors duration-200',
isLocked
? 'cursor-not-allowed bg-gray-200 text-gray-300 dark:bg-gray-500 dark:text-gray-600'
: isDeleting
? 'cursor-not-allowed bg-gray-100 text-gray-700 dark:bg-gray-400 dark:text-gray-700'
: 'bg-red-700 text-white hover:bg-red-800 ',
)}
onClick={handleDeleteUser}
disabled={isDeleting || isLocked}
>
{isDeleting ? (
<div className="flex h-6 justify-center">
<Spinner className="icon-sm m-auto" />
</div>
) : (
<>
{isLocked ? (
<>
<LockIcon />
<span className="ml-2">{localize('com_ui_locked')}</span>
</>
) : (
<>
<LockIcon />
<span className="ml-2">{localize('com_nav_delete_account_button')}</span>
</>
)}
</>
)}
</button>
);
export default DeleteAccount;