Skip to content

Commit

Permalink
Feat: Added the checks before deleting account (#513)
Browse files Browse the repository at this point in the history
* added the checks before deleting account

* added recommended changes

* added recommended changes

* added cross button in modal
  • Loading branch information
Kashyap1ankit authored Oct 19, 2024
1 parent c152c8c commit 6d0630a
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 29 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"seed": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' prisma/seed.ts"
},
"dependencies": {
"100xdevs-job-board": "file:",
"@aws-sdk/client-s3": "^3.645.0",
"@aws-sdk/s3-request-presigner": "^3.645.0",
"@emotion/react": "^11.13.3",
Expand Down Expand Up @@ -58,7 +59,6 @@
"@types/lodash": "^4.17.7",
"@types/uuid": "^10.0.0",
"@uidotdev/usehooks": "^2.4.1",
"100xdevs-job-board": "file:",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand All @@ -76,6 +76,7 @@
"nextjs-toploader": "^3.7.15",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.15",
"randomstring": "^1.3.0",
"react": "^18",
"react-day-picker": "^8.10.1",
"react-dom": "^18",
Expand All @@ -95,6 +96,7 @@
"@types/node": "^20.16.10",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.16",
"@types/randomstring": "^1.3.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^8.1.0",
Expand Down
107 changes: 85 additions & 22 deletions src/components/profile/DeleteAccountDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use client';

import { useTransition } from 'react';
import { ClipboardEvent, useEffect, useState, useTransition } from 'react';

import { signOut, useSession } from 'next-auth/react';
import { useToast } from '../ui/use-toast';

import { Button } from '../ui/button';
import Loader from '../loader';

import {
Dialog,
Expand All @@ -15,17 +14,43 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
} from '@/components/ui/dialog';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import Randomstring from 'randomstring';
import {
UserProfileDestroySchema,
UserProfileDestroyType,
} from '@/lib/validators/user.profile.validator';

import { deleteUser } from '@/actions/user.profile.actions';
import { Trash } from 'lucide-react';
import { Trash, X } from 'lucide-react';
import { FaSpinner } from 'react-icons/fa';

export const DeleteAccountDialog = () => {
const { toast } = useToast();
const session = useSession();

const {
register,
handleSubmit,
formState: { errors },
} = useForm<UserProfileDestroyType>({
resolver: zodResolver(UserProfileDestroySchema),
});

const [isPending, startTransition] = useTransition();
const [modalOpen, setModalOpen] = useState(false);
const [randomString, setRandomString] = useState<string>('');

const [disabled, setDisabled] = useState(true);

function handleCheck(e: any) {
if (e.target.value === randomString) {
return setDisabled(false);
}
setDisabled(true);
}

const handleDeleteAccount = async () => {
startTransition(() => {
Expand All @@ -46,39 +71,77 @@ export const DeleteAccountDialog = () => {
});
});
};

useEffect(() => {
setRandomString(Randomstring.generate(8));
}, []);

return (
<Dialog>
<Dialog open={modalOpen}>
<DialogTrigger asChild>
<Button className="w-40 flex gap-2 text-red-500" variant={'ghost'}>
<Button
className="w-40 flex gap-2 text-red-500"
variant={'ghost'}
onClick={() => setModalOpen(true)}
>
<Trash size={15} />
Delete Account
</Button>
</DialogTrigger>
<DialogContent>

<DialogContent hideCloseButton>
<div className="flex justify-end">
<X
className="cursor-pointer size-4"
onClick={() => setModalOpen(false)}
/>
</div>
<DialogHeader className="text-start">
<DialogTitle>
Are you sure you want to delete your account?
</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your
account and all associated data.
<form className="mt-6" onSubmit={handleSubmit(handleDeleteAccount)}>
<label className="text-black dark:text-gray-200" htmlFor="random">
Type{' '}
<span className="bg-gray-300 dark:bg-gray-800 text-black dark:text-white font-mono px-2 py-1 rounded">
{randomString}
</span>
</label>
<input
{...register('random')}
id="random"
className=" mt-2 p-2 rounded-md font-bold p-4 rounded-md w-full bg-gray-200 dark:bg-black outline-none text-black dark:text-white"
onPaste={(e: ClipboardEvent<HTMLInputElement>) =>
e.preventDefault()
}
onChange={handleCheck}
/>

{errors.random?.message && (
<p className="mt-2 text-red-500">{errors.random?.message}</p>
)}

<div className="flex gap-2 items-baseline">
<Button
disabled={disabled}
className="mt-4 bg-red-500 hover:bg-red-500 text-white"
>
{isPending ? <FaSpinner className="animate-spin" /> : 'Yes'}
</Button>

<div
className="bg-gray-200 dark:bg-transparent text-black dark:text-white hover:bg-gray-200 hover:dark:bg-transparent border-2 dark:border-slate-500 py-2 px-4 cursor-pointer rounded-md"
onClick={() => setModalOpen(false)}
>
<p>No</p>
</div>
</div>
</form>
</DialogDescription>
</DialogHeader>
<div className="flex gap-4 justify-end">
<Button
className="w-36"
variant={'destructive'}
onClick={handleDeleteAccount}
disabled={isPending}
>
{isPending ? <Loader /> : 'Delete Account'}
</Button>
<DialogClose asChild>
<Button variant={'ghost'} disabled={isPending}>
Cancel
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
);
Expand Down
16 changes: 10 additions & 6 deletions src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
hideCloseButton?: boolean;
}
>(({ className, children, hideCloseButton = false, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
Expand All @@ -44,10 +46,12 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
{!hideCloseButton && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
));
Expand Down
9 changes: 9 additions & 0 deletions src/lib/validators/user.profile.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,12 @@ export type projectSchemaType = z.infer<typeof projectSchema>;
export type expFormSchemaType = z.infer<typeof expFormSchema>;
export type addSkillsSchemaType = z.infer<typeof addSkillsSchema>;
export type UserPasswordSchemaType = z.infer<typeof UserPasswordSchema>;

export const UserProfileDestroySchema = z.object({
random: z
.string({ message: 'Required' })
.min(1, { message: 'Min 1 char long' })
.max(8, { message: "Can't be more long" }),
});

export type UserProfileDestroyType = z.infer<typeof UserProfileDestroySchema>;

0 comments on commit 6d0630a

Please sign in to comment.