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

View File

@ -29,9 +29,9 @@ export const Input: React.FC<Props> = ({
type={type}
id={id}
value={value}
{...(register && register(name, validations))}
{...(register && register(name ?? "", validations))}
onChange={(e) => {
register && register(name).onChange(e);
register && register(name ?? "").onChange(e);
onChange && onChange(e);
}}
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"> {
label?: string;
name: string;
name?: string;
value?: string | number | readonly string[];
mode?: "primary" | "transparent" | "trueTransparent" | "secondary" | "disabled";
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 { mutate } from "swr";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
@ -26,57 +28,63 @@ type Props = {
user: ICurrentUserResponse | undefined;
};
const defaultValues = {
workspaceName: "",
confirmDelete: "",
};
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 { setToastAlert } = useToast();
useEffect(() => {
if (data) setSelectedWorkspace(data);
else {
const timer = setTimeout(() => {
setSelectedWorkspace(null);
clearTimeout(timer);
}, 350);
}
}, [data]);
const {
control,
formState: { isSubmitting },
handleSubmit,
reset,
watch,
} = useForm({ defaultValues });
const canDelete = confirmWorkspaceName === data?.name && confirmDeleteMyWorkspace;
const canDelete =
watch("workspaceName") === data?.name && watch("confirmDelete") === "delete my workspace";
const handleClose = () => {
setIsDeleteLoading(false);
setConfirmWorkspaceName("");
setConfirmDeleteMyWorkspace(false);
const timer = setTimeout(() => {
reset(defaultValues);
clearTimeout(timer);
}, 350);
onClose();
};
const handleDeletion = async () => {
setIsDeleteLoading(true);
const onSubmit = async () => {
if (!data || !canDelete) return;
await workspaceService
.deleteWorkspace(data.slug, user)
.then(() => {
handleClose();
router.push("/");
mutate<IWorkspace[]>(USER_WORKSPACES, (prevData) =>
prevData?.filter((workspace) => workspace.id !== data.id)
);
setToastAlert({
type: "success",
message: "Workspace deleted successfully",
title: "Success",
title: "Success!",
message: "Workspace deleted successfully.",
});
})
.catch((error) => {
console.log(error);
setIsDeleteLoading(false);
});
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Something went wrong. Please try again later.",
})
);
};
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"
>
<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">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<ExclamationTriangleIcon
@ -131,20 +139,21 @@ export const DeleteWorkspaceModal: React.FC<Props> = ({ isOpen, data, onClose, u
<div className="text-custom-text-200">
<p className="break-words text-sm ">
Enter the workspace name{" "}
<span className="font-medium text-custom-text-100">
{selectedWorkspace?.name}
</span>{" "}
to continue:
<span className="font-medium text-custom-text-100">{data?.name}</span> to
continue:
</p>
<Controller
control={control}
name="workspaceName"
render={({ field: { onChange, value } }) => (
<Input
type="text"
placeholder="Workspace name"
className="mt-2"
value={confirmWorkspaceName}
onChange={(e) => {
setConfirmWorkspaceName(e.target.value);
}}
name="workspaceName"
onChange={onChange}
value={value}
/>
)}
/>
</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>{" "}
below:
</p>
<Controller
control={control}
name="confirmDelete"
render={({ field: { onChange, value } }) => (
<Input
type="text"
placeholder="Enter 'delete my workspace'"
className="mt-2"
onChange={(e) => {
if (e.target.value === "delete my workspace") {
setConfirmDeleteMyWorkspace(true);
} else {
setConfirmDeleteMyWorkspace(false);
}
}}
name="typeDelete"
onChange={onChange}
value={value}
/>
)}
/>
</div>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDeletion} loading={isDeleteLoading || !canDelete}>
{isDeleteLoading ? "Deleting..." : "Delete Workspace"}
<DangerButton type="submit" disabled={!canDelete} loading={isSubmitting}>
{isSubmitting ? "Deleting..." : "Delete Workspace"}
</DangerButton>
</div>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>