From 74a27edb09e8b45ca16647e7d03ba7365e083630 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Mon, 12 Dec 2022 14:08:11 +0530 Subject: [PATCH] feat: confirm project delete by typing refractor: organised imports, changed create-project-model filename to kabab-case, and made modal more bug free --- ...etion.tsx => confirm-project-deletion.tsx} | 75 ++++++++++++++++--- .../components/project/memberInvitations.tsx | 4 +- apps/app/pages/projects/index.tsx | 55 +++++++------- 3 files changed, 96 insertions(+), 38 deletions(-) rename apps/app/components/project/{ConfirmProjectDeletion.tsx => confirm-project-deletion.tsx} (63%) diff --git a/apps/app/components/project/ConfirmProjectDeletion.tsx b/apps/app/components/project/confirm-project-deletion.tsx similarity index 63% rename from apps/app/components/project/ConfirmProjectDeletion.tsx rename to apps/app/components/project/confirm-project-deletion.tsx index 0b0dde39e..46391bb5c 100644 --- a/apps/app/components/project/ConfirmProjectDeletion.tsx +++ b/apps/app/components/project/confirm-project-deletion.tsx @@ -9,19 +9,26 @@ import useToast from "lib/hooks/useToast"; // icons import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui -import { Button } from "ui"; +import { Button, Input } from "ui"; // types import type { IProject } from "types"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; - data?: IProject; + onClose: () => void; + data: IProject | null; }; -const ConfirmProjectDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { +const ConfirmProjectDeletion: React.FC = ({ isOpen, data, onClose }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [selectedProject, setSelectedProject] = useState(null); + + const [confirmProjectName, setConfirmProjectName] = useState(""); + const [confirmDeleteMyProject, setConfirmDeleteMyProject] = useState(false); + + const canDelete = confirmProjectName === data?.name && confirmDeleteMyProject; + const { activeWorkspace, mutateProjects } = useUser(); const { setToastAlert } = useToast(); @@ -29,13 +36,18 @@ const ConfirmProjectDeletion: React.FC = ({ isOpen, setIsOpen, data }) => const cancelButtonRef = useRef(null); const handleClose = () => { - setIsOpen(false); setIsDeleteLoading(false); + const timer = setTimeout(() => { + setConfirmProjectName(""); + setConfirmDeleteMyProject(false); + clearTimeout(timer); + }, 350); + onClose(); }; const handleDeletion = async () => { setIsDeleteLoading(true); - if (!data || !activeWorkspace) return; + if (!data || !activeWorkspace || !canDelete) return; await projectService .deleteProject(activeWorkspace.slug, data.id) .then(() => { @@ -54,8 +66,14 @@ const ConfirmProjectDeletion: React.FC = ({ isOpen, setIsOpen, data }) => }; useEffect(() => { - data && setIsOpen(true); - }, [data, setIsOpen]); + if (data) setSelectedProject(data); + else { + const timer = setTimeout(() => { + setSelectedProject(null); + clearTimeout(timer); + }, 300); + } + }, [data]); return ( @@ -104,11 +122,48 @@ const ConfirmProjectDeletion: React.FC = ({ isOpen, setIsOpen, data }) =>

Are you sure you want to delete project - {`"`} - {data?.name} + {selectedProject?.name} {`"`} ? All of the data related to the project will be permanently removed. This action cannot be undone.

+
+
+

+ Enter the project name{" "} + {selectedProject?.name} to + continue: +

+ { + setConfirmProjectName(e.target.value); + }} + name="projectName" + /> +
+
+

+ To confirm, type delete my project{" "} + below: +

+ { + if (e.target.value === "delete my project") { + setConfirmDeleteMyProject(true); + } else { + setConfirmDeleteMyProject(false); + } + }} + name="projectName" + /> +
@@ -117,7 +172,7 @@ const ConfirmProjectDeletion: React.FC = ({ isOpen, setIsOpen, data }) => type="button" onClick={handleDeletion} theme="danger" - disabled={isDeleteLoading} + disabled={isDeleteLoading || !canDelete} className="inline-flex sm:ml-3" > {isDeleteLoading ? "Deleting..." : "Delete"} diff --git a/apps/app/components/project/memberInvitations.tsx b/apps/app/components/project/memberInvitations.tsx index 46804695f..4ecf8e1b3 100644 --- a/apps/app/components/project/memberInvitations.tsx +++ b/apps/app/components/project/memberInvitations.tsx @@ -28,7 +28,7 @@ type Props = { slug: string; invitationsRespond: string[]; handleInvitation: (project_invitation: any, action: "accepted" | "withdraw") => void; - setDeleteProject: React.Dispatch>; + setDeleteProject: (id: string | null) => void; }; const ProjectMemberInvitations: React.FC = ({ @@ -100,7 +100,7 @@ const ProjectMemberInvitations: React.FC = ({ diff --git a/apps/app/pages/projects/index.tsx b/apps/app/pages/projects/index.tsx index 3f17d3552..cb1b29ac6 100644 --- a/apps/app/pages/projects/index.tsx +++ b/apps/app/pages/projects/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; // next import type { NextPage } from "next"; // hooks @@ -8,23 +8,25 @@ import withAuth from "lib/hoc/withAuthWrapper"; // layouts import AppLayout from "layouts/AppLayout"; // components -import CreateProjectModal from "components/project/create-project-modal"; -import ConfirmProjectDeletion from "components/project/ConfirmProjectDeletion"; +import ProjectMemberInvitations from "components/project/memberInvitations"; +import ConfirmProjectDeletion from "components/project/confirm-project-deletion"; // ui -import { Button, Spinner } from "ui"; -// types -import { IProject } from "types"; +import { + Button, + Spinner, + HeaderButton, + Breadcrumbs, + BreadcrumbItem, + EmptySpace, + EmptySpaceItem, +} from "ui"; // services import projectService from "lib/services/project.service"; -import ProjectMemberInvitations from "components/project/memberInvitations"; +// icons import { ClipboardDocumentListIcon, PlusIcon } from "@heroicons/react/24/outline"; -import { BreadcrumbItem, Breadcrumbs } from "ui/Breadcrumbs"; -import { EmptySpace, EmptySpaceItem } from "ui/EmptySpace"; -import HeaderButton from "ui/HeaderButton"; const Projects: NextPage = () => { - const [isOpen, setIsOpen] = useState(false); - const [deleteProject, setDeleteProject] = useState(); + const [deleteProject, setDeleteProject] = useState(null); const [invitationsRespond, setInvitationsRespond] = useState([]); const { projects, activeWorkspace, mutateProjects } = useUser(); @@ -54,21 +56,12 @@ const Projects: NextPage = () => { }); }; - useEffect(() => { - if (isOpen) return; - const timer = setTimeout(() => { - setDeleteProject(undefined); - clearTimeout(timer); - }, 300); - }, [isOpen]); - return ( - setDeleteProject(null)} + data={projects?.find((item) => item.id === deleteProject) ?? null} /> {projects ? ( <> @@ -89,7 +82,10 @@ const Projects: NextPage = () => { } Icon={PlusIcon} - action={() => setIsOpen(true)} + action={() => { + const e = new KeyboardEvent("keydown", { key: "p", ctrlKey: true }); + document.dispatchEvent(e); + }} /> @@ -100,7 +96,14 @@ const Projects: NextPage = () => {

Projects

- setIsOpen(true)} /> + { + const e = new KeyboardEvent("keydown", { key: "p", ctrlKey: true }); + document.dispatchEvent(e); + }} + />
{projects.map((item) => (