refactor: project and workspace delete modals (#1915)

This commit is contained in:
Aaryan Khandelwal 2023-08-21 11:44:59 +05:30 committed by GitHub
parent d470adf262
commit 2eb956e97e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 122 deletions

View File

@ -1,9 +1,11 @@
import React, { useEffect, useState } from "react"; import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// services // services
@ -27,6 +29,11 @@ type TConfirmProjectDeletionProps = {
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
}; };
const defaultValues = {
projectName: "",
confirmDelete: "",
};
export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
isOpen, isOpen,
data, data,
@ -34,51 +41,41 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
onSuccess, onSuccess,
user, user,
}) => { }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [confirmProjectName, setConfirmProjectName] = useState("");
const [confirmDeleteMyProject, setConfirmDeleteMyProject] = useState(false);
const [selectedProject, setSelectedProject] = useState<IProject | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const canDelete = confirmProjectName === data?.name && confirmDeleteMyProject; const {
control,
formState: { isSubmitting },
handleSubmit,
reset,
watch,
} = useForm({ defaultValues });
useEffect(() => { const canDelete =
if (data) setSelectedProject(data); watch("projectName") === data?.name && watch("confirmDelete") === "delete my project";
else {
const timer = setTimeout(() => {
setSelectedProject(null);
clearTimeout(timer);
}, 300);
}
}, [data]);
const handleClose = () => { const handleClose = () => {
setIsDeleteLoading(false);
const timer = setTimeout(() => { const timer = setTimeout(() => {
setConfirmProjectName(""); reset(defaultValues);
setConfirmDeleteMyProject(false);
clearTimeout(timer); clearTimeout(timer);
}, 350); }, 350);
onClose(); onClose();
}; };
const handleDeletion = async () => { const onSubmit = async () => {
if (!data || !workspaceSlug || !canDelete) return; if (!data || !workspaceSlug || !canDelete) return;
setIsDeleteLoading(true);
await projectService await projectService
.deleteProject(workspaceSlug as string, data.id, user) .deleteProject(workspaceSlug.toString(), data.id, user)
.then(() => { .then(() => {
handleClose(); handleClose();
mutate<IProject[]>( mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), PROJECTS_LIST(workspaceSlug.toString(), { is_favorite: "all" }),
(prevData) => prevData?.filter((project: IProject) => project.id !== data.id), (prevData) => prevData?.filter((project: IProject) => project.id !== data.id),
false false
); );
@ -91,8 +88,7 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
title: "Error!", title: "Error!",
message: "Something went wrong. Please try again later.", message: "Something went wrong. Please try again later.",
}) })
) );
.finally(() => setIsDeleteLoading(false));
}; };
return ( return (
@ -122,7 +118,7 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl"> <Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6"> <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6"> <div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4"> <span className="place-items-center rounded-full bg-red-500/20 p-4">
<ExclamationTriangleIcon <ExclamationTriangleIcon
@ -137,28 +133,29 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
<span> <span>
<p className="text-sm leading-7 text-custom-text-200"> <p className="text-sm leading-7 text-custom-text-200">
Are you sure you want to delete project{" "} Are you sure you want to delete project{" "}
<span className="break-words font-semibold">{selectedProject?.name}</span>? <span className="break-words font-semibold">{data?.name}</span>? All of the
All of the data related to the project will be permanently removed. This data related to the project will be permanently removed. This action cannot be
action cannot be undone undone
</p> </p>
</span> </span>
<div className="text-custom-text-200"> <div className="text-custom-text-200">
<p className="break-words text-sm "> <p className="break-words text-sm ">
Enter the project name{" "} Enter the project name{" "}
<span className="font-medium text-custom-text-100"> <span className="font-medium text-custom-text-100">{data?.name}</span> to
{selectedProject?.name} continue:
</span>{" "}
to continue:
</p> </p>
<Controller
control={control}
name="projectName"
render={({ field: { onChange, value } }) => (
<Input <Input
type="text" type="text"
placeholder="Project name" placeholder="Project name"
className="mt-2" className="mt-2"
value={confirmProjectName} value={value}
onChange={(e) => { onChange={onChange}
setConfirmProjectName(e.target.value); />
}} )}
name="projectName"
/> />
</div> </div>
<div className="text-custom-text-200"> <div className="text-custom-text-200">
@ -167,31 +164,27 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
<span className="font-medium text-custom-text-100">delete my project</span>{" "} <span className="font-medium text-custom-text-100">delete my project</span>{" "}
below: below:
</p> </p>
<Controller
control={control}
name="confirmDelete"
render={({ field: { onChange, value } }) => (
<Input <Input
type="text" type="text"
placeholder="Enter 'delete my project'" placeholder="Enter 'delete my project'"
className="mt-2" className="mt-2"
onChange={(e) => { onChange={onChange}
if (e.target.value === "delete my project") { value={value}
setConfirmDeleteMyProject(true); />
} else { )}
setConfirmDeleteMyProject(false);
}
}}
name="typeDelete"
/> />
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton> <SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton <DangerButton type="submit" disabled={!canDelete} loading={isSubmitting}>
onClick={handleDeletion} {isSubmitting ? "Deleting..." : "Delete Project"}
disabled={!canDelete}
loading={isDeleteLoading}
>
{isDeleteLoading ? "Deleting..." : "Delete Project"}
</DangerButton> </DangerButton>
</div> </div>
</div> </form>
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
</div> </div>

View File

@ -29,9 +29,9 @@ export const Input: React.FC<Props> = ({
type={type} type={type}
id={id} id={id}
value={value} value={value}
{...(register && register(name, validations))} {...(register && register(name ?? "", validations))}
onChange={(e) => { onChange={(e) => {
register && register(name).onChange(e); register && register(name ?? "").onChange(e);
onChange && onChange(e); onChange && onChange(e);
}} }}
className={`block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${ className={`block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${

View File

@ -3,7 +3,7 @@ import type { UseFormRegister, RegisterOptions } from "react-hook-form";
export interface Props extends React.ComponentPropsWithoutRef<"input"> { export interface Props extends React.ComponentPropsWithoutRef<"input"> {
label?: string; label?: string;
name: string; name?: string;
value?: string | number | readonly string[]; value?: string | number | readonly string[];
mode?: "primary" | "transparent" | "trueTransparent" | "secondary" | "disabled"; mode?: "primary" | "transparent" | "trueTransparent" | "secondary" | "disabled";
register?: UseFormRegister<any>; register?: UseFormRegister<any>;

View File

@ -1,9 +1,11 @@
import React, { useEffect, useState } from "react"; import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// services // services
@ -26,57 +28,63 @@ type Props = {
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
}; };
const defaultValues = {
workspaceName: "",
confirmDelete: "",
};
export const DeleteWorkspaceModal: React.FC<Props> = ({ isOpen, data, onClose, user }) => { export const DeleteWorkspaceModal: React.FC<Props> = ({ isOpen, data, onClose, user }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [confirmWorkspaceName, setConfirmWorkspaceName] = useState("");
const [confirmDeleteMyWorkspace, setConfirmDeleteMyWorkspace] = useState(false);
const [selectedWorkspace, setSelectedWorkspace] = useState<IWorkspace | null>(null);
const router = useRouter(); const router = useRouter();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
useEffect(() => { const {
if (data) setSelectedWorkspace(data); control,
else { formState: { isSubmitting },
const timer = setTimeout(() => { handleSubmit,
setSelectedWorkspace(null); reset,
clearTimeout(timer); watch,
}, 350); } = useForm({ defaultValues });
}
}, [data]);
const canDelete = confirmWorkspaceName === data?.name && confirmDeleteMyWorkspace; const canDelete =
watch("workspaceName") === data?.name && watch("confirmDelete") === "delete my workspace";
const handleClose = () => { const handleClose = () => {
setIsDeleteLoading(false); const timer = setTimeout(() => {
setConfirmWorkspaceName(""); reset(defaultValues);
setConfirmDeleteMyWorkspace(false); clearTimeout(timer);
}, 350);
onClose(); onClose();
}; };
const handleDeletion = async () => { const onSubmit = async () => {
setIsDeleteLoading(true);
if (!data || !canDelete) return; if (!data || !canDelete) return;
await workspaceService await workspaceService
.deleteWorkspace(data.slug, user) .deleteWorkspace(data.slug, user)
.then(() => { .then(() => {
handleClose(); handleClose();
router.push("/"); router.push("/");
mutate<IWorkspace[]>(USER_WORKSPACES, (prevData) => mutate<IWorkspace[]>(USER_WORKSPACES, (prevData) =>
prevData?.filter((workspace) => workspace.id !== data.id) prevData?.filter((workspace) => workspace.id !== data.id)
); );
setToastAlert({ setToastAlert({
type: "success", type: "success",
message: "Workspace deleted successfully", title: "Success!",
title: "Success", message: "Workspace deleted successfully.",
}); });
}) })
.catch((error) => { .catch(() =>
console.log(error); setToastAlert({
setIsDeleteLoading(false); type: "error",
}); title: "Error!",
message: "Something went wrong. Please try again later.",
})
);
}; };
return ( return (
@ -106,7 +114,7 @@ export const DeleteWorkspaceModal: React.FC<Props> = ({ isOpen, data, onClose, u
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl"> <Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6"> <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6"> <div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4"> <span className="place-items-center rounded-full bg-red-500/20 p-4">
<ExclamationTriangleIcon <ExclamationTriangleIcon
@ -131,20 +139,21 @@ export const DeleteWorkspaceModal: React.FC<Props> = ({ isOpen, data, onClose, u
<div className="text-custom-text-200"> <div className="text-custom-text-200">
<p className="break-words text-sm "> <p className="break-words text-sm ">
Enter the workspace name{" "} Enter the workspace name{" "}
<span className="font-medium text-custom-text-100"> <span className="font-medium text-custom-text-100">{data?.name}</span> to
{selectedWorkspace?.name} continue:
</span>{" "}
to continue:
</p> </p>
<Controller
control={control}
name="workspaceName"
render={({ field: { onChange, value } }) => (
<Input <Input
type="text" type="text"
placeholder="Workspace name" placeholder="Workspace name"
className="mt-2" className="mt-2"
value={confirmWorkspaceName} onChange={onChange}
onChange={(e) => { value={value}
setConfirmWorkspaceName(e.target.value); />
}} )}
name="workspaceName"
/> />
</div> </div>
@ -154,28 +163,28 @@ export const DeleteWorkspaceModal: React.FC<Props> = ({ isOpen, data, onClose, u
<span className="font-medium text-custom-text-100">delete my workspace</span>{" "} <span className="font-medium text-custom-text-100">delete my workspace</span>{" "}
below: below:
</p> </p>
<Controller
control={control}
name="confirmDelete"
render={({ field: { onChange, value } }) => (
<Input <Input
type="text" type="text"
placeholder="Enter 'delete my workspace'" placeholder="Enter 'delete my workspace'"
className="mt-2" className="mt-2"
onChange={(e) => { onChange={onChange}
if (e.target.value === "delete my workspace") { value={value}
setConfirmDeleteMyWorkspace(true); />
} else { )}
setConfirmDeleteMyWorkspace(false);
}
}}
name="typeDelete"
/> />
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton> <SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDeletion} loading={isDeleteLoading || !canDelete}> <DangerButton type="submit" disabled={!canDelete} loading={isSubmitting}>
{isDeleteLoading ? "Deleting..." : "Delete Workspace"} {isSubmitting ? "Deleting..." : "Delete Workspace"}
</DangerButton> </DangerButton>
</div> </div>
</div> </form>
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
</div> </div>