[WEB-447] feat: projects archive. (#4014)

* dev: project archive response

* feat: projects archive.

* dev: response changes for cycle and module

* chore: status message changed

* chore: update clear all applied display filters logic.

* style: archived project card UI update.

* chore: archive/ restore taost message update.

* fix: clear all applied display filter logic.

* chore: project empty state update to handle archived projects.

* chore: minor typo fix in cycles and modules archive.

* chore: close cycle/ module overview sidebar if it's already open when clicked on overview button.

* chore: optimize current workspace applied display filter logic.

* chore: update all `archived_at` type from `Date` to `string`.

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
Prateek Shourya 2024-03-21 20:59:34 +05:30 committed by GitHub
parent 9642b761b7
commit 231fd52992
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 749 additions and 162 deletions

View file

@ -0,0 +1,135 @@
import { useState, Fragment } from "react";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
// ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// hooks
import { useProject } from "@/hooks/store";
type Props = {
workspaceSlug: string;
projectId: string;
isOpen: boolean;
onClose: () => void;
archive: boolean;
};
export const ArchiveRestoreProjectModal: React.FC<Props> = (props) => {
const { workspaceSlug, projectId, isOpen, onClose, archive } = props;
// router
const router = useRouter();
// states
const [isLoading, setIsLoading] = useState(false);
// store hooks
const { getProjectById, archiveProject, restoreProject } = useProject();
const projectDetails = getProjectById(projectId);
if (!projectDetails) return null;
const handleClose = () => {
setIsLoading(false);
onClose();
};
const handleArchiveProject = async () => {
setIsLoading(true);
await archiveProject(workspaceSlug, projectId)
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Archive success",
message: `${projectDetails.name} has been archived successfully`,
});
onClose();
router.push(`/${workspaceSlug}/projects/`);
})
.catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Project could not be archived. Please try again.",
})
)
.finally(() => setIsLoading(false));
};
const handleRestoreProject = async () => {
setIsLoading(true);
await restoreProject(workspaceSlug, projectId)
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Restore success",
message: `You can find ${projectDetails.name} in your projects.`,
});
onClose();
router.push(`/${workspaceSlug}/projects/`);
})
.catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Project could not be restored. Please try again.",
})
)
.finally(() => setIsLoading(false));
};
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="px-5 py-4">
<h3 className="text-xl font-medium 2xl:text-2xl">
{archive ? "Archive" : "Restore"} {projectDetails.name}
</h3>
<p className="mt-3 text-sm text-custom-text-200">
{archive
? "This project and its issues, cycles, modules, and pages will be archived. Its issues wont appear in search. Only project admins can restore the project."
: "Restoring a project will activate it and make it visible to all members of the project. Are you sure you want to continue?"}
</p>
<div className="mt-3 flex justify-end gap-2">
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button
size="sm"
tabIndex={1}
onClick={archive ? handleArchiveProject : handleRestoreProject}
loading={isLoading}
>
{archive ? (isLoading ? "Archiving" : "Archive") : isLoading ? "Restoring" : "Restore"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};

View file

@ -0,0 +1,2 @@
export * from "./selection";
export * from "./archive-restore-modal";

View file

@ -0,0 +1,60 @@
import React from "react";
import { ChevronRight, ChevronUp } from "lucide-react";
import { Disclosure, Transition } from "@headlessui/react";
// types
import { IProject } from "@plane/types";
// ui
import { Button, Loader } from "@plane/ui";
export interface IArchiveProject {
projectDetails: IProject;
handleArchive: () => void;
}
export const ArchiveProjectSelection: React.FC<IArchiveProject> = (props) => {
const { projectDetails, handleArchive } = props;
return (
<Disclosure as="div" className="border-t border-custom-border-100 py-4">
{({ open }) => (
<div className="w-full">
<Disclosure.Button as="button" type="button" className="flex w-full items-center justify-between">
<span className="text-xl tracking-tight">Archive project</span>
{open ? <ChevronUp className="h-5 w-5" /> : <ChevronRight className="h-5 w-5" />}
</Disclosure.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
<div className="flex flex-col gap-8 pt-4">
<span className="text-sm tracking-tight">
Archiving a project will unlist your project from your side navigation although you will still be able
to access it from your projects page. You can restore the project or delete it whenever you want.
</span>
<div>
{projectDetails ? (
<div>
<Button variant="outline-danger" onClick={handleArchive}>
Archive project
</Button>
</div>
) : (
<Loader className="mt-2 w-full">
<Loader.Item height="38px" width="144px" />
</Loader>
)}
</div>
</div>
</Disclosure.Panel>
</Transition>
</div>
)}
</Disclosure>
);
};

View file

@ -1,12 +1,10 @@
import React from "react";
// ui
import { ChevronDown, ChevronUp } from "lucide-react";
import { ChevronRight, ChevronUp } from "lucide-react";
import { Disclosure, Transition } from "@headlessui/react";
import { IProject } from "@plane/types";
import { Button, Loader } from "@plane/ui";
// icons
// types
import { IProject } from "@plane/types";
// ui
import { Button, Loader } from "@plane/ui";
export interface IDeleteProjectSection {
projectDetails: IProject;
@ -17,12 +15,12 @@ export const DeleteProjectSection: React.FC<IDeleteProjectSection> = (props) =>
const { projectDetails, handleDelete } = props;
return (
<Disclosure as="div" className="border-t border-custom-border-100">
<Disclosure as="div" className="border-t border-custom-border-100 py-4">
{({ open }) => (
<div className="w-full">
<Disclosure.Button as="button" type="button" className="flex w-full items-center justify-between py-4">
<Disclosure.Button as="button" type="button" className="flex w-full items-center justify-between">
<span className="text-xl tracking-tight">Delete Project</span>
{open ? <ChevronUp className="h-5 w-5" /> : <ChevronDown className="h-5 w-5" />}
{open ? <ChevronUp className="h-5 w-5" /> : <ChevronRight className="h-5 w-5" />}
</Disclosure.Button>
<Transition
@ -35,7 +33,7 @@ export const DeleteProjectSection: React.FC<IDeleteProjectSection> = (props) =>
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-8 pt-4">
<span className="text-sm tracking-tight">
The danger zone of the project delete page is a critical area that requires careful consideration and
attention. When deleting a project, all of the data and resources within that project will be

View file

@ -1,2 +1,3 @@
export * from "./delete-project-section";
export * from "./features-list";
export * from "./archive-project";