From 90776237f3c80767b08768fadd6404b774418c28 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 17 Oct 2023 20:34:16 +0530 Subject: [PATCH] fix: command palette changes (#2465) --- .../{command-k.tsx => command-modal.tsx} | 66 ++++--- .../command-palette/command-pallette.tsx | 163 ++++++++++++------ web/components/command-palette/index.ts | 2 +- .../command-palette/shortcuts-modal.tsx | 19 +- .../core/modals/bulk-delete-issues-modal.tsx | 14 +- web/components/cycles/modal.tsx | 20 +-- web/components/cycles/select.tsx | 9 +- web/components/headers/project-views.tsx | 11 +- web/components/modules/modal.tsx | 17 +- .../pages/create-update-page-modal.tsx | 7 +- web/components/pages/pages-view.tsx | 43 +++-- .../project/create-project-modal.tsx | 32 ++-- web/components/project/sidebar-list.tsx | 16 +- web/components/views/modal.tsx | 46 ++--- web/layouts/app-layout/layout.tsx | 2 +- .../projects/[projectId]/modules/index.tsx | 18 +- .../projects/[projectId]/pages/index.tsx | 22 ++- web/store/command-palette.store.ts | 161 +++++++++++++++++ web/store/root.ts | 11 +- web/store/theme.store.ts | 14 +- web/store/user.store.ts | 4 +- 21 files changed, 469 insertions(+), 228 deletions(-) rename web/components/command-palette/{command-k.tsx => command-modal.tsx} (95%) create mode 100644 web/store/command-palette.store.ts diff --git a/web/components/command-palette/command-k.tsx b/web/components/command-palette/command-modal.tsx similarity index 95% rename from web/components/command-palette/command-k.tsx rename to web/components/command-palette/command-modal.tsx index 7e89d3844..bf4da0f30 100644 --- a/web/components/command-palette/command-k.tsx +++ b/web/components/command-palette/command-modal.tsx @@ -1,12 +1,7 @@ import React, { useCallback, useEffect, useState } from "react"; - import { useRouter } from "next/router"; - import useSWR, { mutate } from "swr"; - -// cmdk import { Command } from "cmdk"; -// headless ui import { Dialog, Transition } from "@headlessui/react"; // services import { WorkspaceService } from "services/workspace.service"; @@ -60,16 +55,20 @@ import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; type Props = { deleteIssue: () => void; isPaletteOpen: boolean; - setIsPaletteOpen: React.Dispatch>; + closePalette: () => void; }; // services const workspaceService = new WorkspaceService(); const issueService = new IssueService(); -export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPaletteOpen }) => { +export const CommandModal: React.FC = (props) => { + const { deleteIssue, isPaletteOpen, closePalette } = props; + // router + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + // states const [placeholder, setPlaceholder] = useState("Type a command or search..."); - const [resultsCount, setResultsCount] = useState(0); const [isLoading, setIsLoading] = useState(false); const [isSearching, setIsSearching] = useState(false); @@ -86,15 +85,12 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal }, }); const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); - const [pages, setPages] = useState([]); + const page = pages[pages.length - 1]; const debouncedSearchTerm = useDebounce(searchTerm, 500); - const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; - const { setToastAlert } = useToast(); const { user } = useUser(); @@ -141,7 +137,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal const handleIssueAssignees = (assignee: string) => { if (!issueDetails) return; - setIsPaletteOpen(false); + closePalette(); const updatedAssignees = issueDetails.assignees ?? []; if (updatedAssignees.includes(assignee)) { @@ -153,12 +149,12 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal }; const redirect = (path: string) => { - setIsPaletteOpen(false); + closePalette(); router.push(path); }; const createNewWorkspace = () => { - setIsPaletteOpen(false); + closePalette(); router.push("/create-workspace"); }; @@ -236,7 +232,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal }} as={React.Fragment} > - setIsPaletteOpen(false)}> + closePalette()}> = ({ deleteIssue, isPaletteOpen, setIsPal // when search is empty and page is undefined // when user tries to close the modal with esc if (e.key === "Escape" && !page && !searchTerm) { - setIsPaletteOpen(false); + closePalette(); } // Escape goes to previous page // Backspace goes to previous page when search is empty @@ -365,7 +361,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); router.push(currentSection.path(item)); }} value={`${key}-${item?.name}`} @@ -388,7 +384,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); setPlaceholder("Change state..."); setSearchTerm(""); setPages([...pages, "change-issue-state"]); @@ -455,7 +451,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); copyIssueUrlToClipboard(); }} className="focus:outline-none" @@ -470,7 +466,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "c", }); @@ -490,7 +486,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "p", }); @@ -512,7 +508,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "q", }); @@ -530,7 +526,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "m", }); @@ -548,7 +544,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "v", }); @@ -566,7 +562,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "d", }); @@ -623,7 +619,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); const e = new KeyboardEvent("keydown", { key: "h", }); @@ -638,7 +634,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); window.open("https://docs.plane.so/", "_blank"); }} className="focus:outline-none" @@ -650,7 +646,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); window.open("https://discord.com/invite/A92xrEGCge", "_blank"); }} className="focus:outline-none" @@ -662,7 +658,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); window.open("https://github.com/makeplane/plane/issues/new/choose", "_blank"); }} className="focus:outline-none" @@ -674,7 +670,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal { - setIsPaletteOpen(false); + closePalette(); (window as any).$crisp.push(["do", "chat:open"]); }} className="focus:outline-none" @@ -747,15 +743,15 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal )} {page === "change-issue-state" && issueDetails && ( - + )} {page === "change-issue-priority" && issueDetails && ( - + )} {page === "change-issue-assignee" && issueDetails && ( - + )} - {page === "change-interface-theme" && } + {page === "change-interface-theme" && } diff --git a/web/components/command-palette/command-pallette.tsx b/web/components/command-palette/command-pallette.tsx index 4b2168137..e42317a6b 100644 --- a/web/components/command-palette/command-pallette.tsx +++ b/web/components/command-palette/command-pallette.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, FC } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { observer } from "mobx-react-lite"; @@ -6,7 +6,7 @@ import { observer } from "mobx-react-lite"; import useToast from "hooks/use-toast"; import useUser from "hooks/use-user"; // components -import { CommandK, ShortcutsModal } from "components/command-palette"; +import { CommandModal, ShortcutsModal } from "components/command-palette"; import { BulkDeleteIssuesModal } from "components/core"; import { CreateUpdateCycleModal } from "components/cycles"; import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; @@ -26,22 +26,34 @@ import { useMobxStore } from "lib/mobx/store-provider"; // services const issueService = new IssueService(); -export const CommandPalette: React.FC = observer(() => { - const store: any = useMobxStore(); - - const [isPaletteOpen, setIsPaletteOpen] = useState(false); - const [isIssueModalOpen, setIsIssueModalOpen] = useState(false); - const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); - const [isShortcutsModalOpen, setIsShortcutsModalOpen] = useState(false); - const [isCreateCycleModalOpen, setIsCreateCycleModalOpen] = useState(false); - const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false); - const [isCreateModuleModalOpen, setIsCreateModuleModalOpen] = useState(false); - const [isBulkDeleteIssuesModalOpen, setIsBulkDeleteIssuesModalOpen] = useState(false); - const [deleteIssueModal, setDeleteIssueModal] = useState(false); - const [isCreateUpdatePageModalOpen, setIsCreateUpdatePageModalOpen] = useState(false); - +export const CommandPalette: FC = observer(() => { const router = useRouter(); const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query; + // store + const { commandPalette, theme: themeStore } = useMobxStore(); + const { + isCommandPaletteOpen, + toggleCommandPaletteModal, + isCreateIssueModalOpen, + toggleCreateIssueModal, + isCreateCycleModalOpen, + toggleCreateCycleModal, + isCreatePageModalOpen, + toggleCreatePageModal, + isCreateProjectModalOpen, + toggleCreateProjectModal, + isCreateModuleModalOpen, + toggleCreateModuleModal, + isCreateViewModalOpen, + toggleCreateViewModal, + isShortcutModalOpen, + toggleShortcutModal, + isBulkDeleteIssueModalOpen, + toggleBulkDeleteIssueModal, + isDeleteIssueModalOpen, + toggleDeleteIssueModal, + } = commandPalette; + const { setSidebarCollapsed } = themeStore; const { user } = useUser(); @@ -55,7 +67,7 @@ export const CommandPalette: React.FC = observer(() => { ); const copyIssueUrlToClipboard = useCallback(() => { - if (!router.query.issueId) return; + if (!issueId) return; const url = new URL(window.location.href); copyTextToClipboard(url.href) @@ -71,7 +83,7 @@ export const CommandPalette: React.FC = observer(() => { title: "Some error occurred", }); }); - }, [router, setToastAlert]); + }, [setToastAlert, issueId]); const handleKeyDown = useCallback( (e: KeyboardEvent) => { @@ -91,101 +103,142 @@ export const CommandPalette: React.FC = observer(() => { if (cmdClicked) { if (keyPressed === "k") { e.preventDefault(); - setIsPaletteOpen(true); + toggleCommandPaletteModal(true); } else if (keyPressed === "c" && altKey) { e.preventDefault(); copyIssueUrlToClipboard(); } else if (keyPressed === "b") { e.preventDefault(); - store.theme.setSidebarCollapsed(!store?.theme?.sidebarCollapsed); + setSidebarCollapsed(); } } else { if (keyPressed === "c") { - setIsIssueModalOpen(true); + toggleCreateIssueModal(true); } else if (keyPressed === "p") { - setIsProjectModalOpen(true); + toggleCreateProjectModal(true); } else if (keyPressed === "v") { - setIsCreateViewModalOpen(true); + toggleCreateViewModal(true); } else if (keyPressed === "d") { - setIsCreateUpdatePageModalOpen(true); + toggleCreatePageModal(true); } else if (keyPressed === "h") { - setIsShortcutsModalOpen(true); + toggleShortcutModal(true); } else if (keyPressed === "q") { - setIsCreateCycleModalOpen(true); + toggleCreateCycleModal(true); } else if (keyPressed === "m") { - setIsCreateModuleModalOpen(true); + toggleCreateModuleModal(true); } else if (keyPressed === "backspace" || keyPressed === "delete") { e.preventDefault(); - setIsBulkDeleteIssuesModalOpen(true); + toggleBulkDeleteIssueModal(true); } } }, - [copyIssueUrlToClipboard, store.theme] + [ + copyIssueUrlToClipboard, + toggleCreateProjectModal, + toggleCreateViewModal, + toggleCreatePageModal, + toggleShortcutModal, + toggleCreateCycleModal, + toggleCreateModuleModal, + toggleBulkDeleteIssueModal, + toggleCommandPaletteModal, + setSidebarCollapsed, + toggleCreateIssueModal, + ] ); useEffect(() => { document.addEventListener("keydown", handleKeyDown); - return () => document.removeEventListener("keydown", handleKeyDown); }, [handleKeyDown]); if (!user) return null; const deleteIssue = () => { - setIsPaletteOpen(false); - setDeleteIssueModal(true); + toggleCommandPaletteModal(false); + toggleDeleteIssueModal(true); }; return ( <> - + { + toggleShortcutModal(false); + }} + /> {workspaceSlug && ( - + { + toggleCreateProjectModal(false); + }} + workspaceSlug={workspaceSlug.toString()} + /> )} - {projectId && ( + {workspaceSlug && projectId && ( <> setIsCreateCycleModalOpen(false)} - user={user} + handleClose={() => toggleCreateCycleModal(false)} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} /> { + toggleCreateModuleModal(false); + }} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} /> setIsCreateViewModalOpen(false)} + onClose={() => toggleCreateViewModal(false)} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} /> setIsCreateUpdatePageModalOpen(false)} + isOpen={isCreatePageModalOpen} + handleClose={() => toggleCreatePageModal(false)} user={user} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} /> )} - {issueId && issueDetails && ( - setDeleteIssueModal(false)} - isOpen={deleteIssueModal} - data={issueDetails} - user={user} - /> - )} + setIsIssueModalOpen(false)} + isOpen={isCreateIssueModalOpen} + handleClose={() => toggleCreateIssueModal(false)} prePopulateData={ cycleId ? { cycle: cycleId.toString() } : moduleId ? { module: moduleId.toString() } : undefined } /> + + {issueId && issueDetails && ( + toggleDeleteIssueModal(false)} + isOpen={isDeleteIssueModalOpen} + data={issueDetails} + user={user} + /> + )} + { + toggleBulkDeleteIssueModal(false); + }} user={user} /> - + { + toggleCommandPaletteModal(false); + }} + /> ); }); diff --git a/web/components/command-palette/index.ts b/web/components/command-palette/index.ts index 6c137d6df..a1230ca32 100644 --- a/web/components/command-palette/index.ts +++ b/web/components/command-palette/index.ts @@ -1,6 +1,6 @@ export * from "./issue"; export * from "./change-interface-theme"; -export * from "./command-k"; +export * from "./command-modal"; export * from "./command-pallette"; export * from "./helpers"; export * from "./shortcuts-modal"; diff --git a/web/components/command-palette/shortcuts-modal.tsx b/web/components/command-palette/shortcuts-modal.tsx index d0ad475e1..85d2b4fe4 100644 --- a/web/components/command-palette/shortcuts-modal.tsx +++ b/web/components/command-palette/shortcuts-modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { FC, useEffect, useState, Dispatch, SetStateAction, Fragment } from "react"; // headless ui import { Dialog, Transition } from "@headlessui/react"; // icons @@ -8,7 +8,7 @@ import { Input } from "@plane/ui"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; + onClose: () => void; }; const shortcuts = [ @@ -43,8 +43,11 @@ const shortcuts = [ const allShortcuts = shortcuts.map((i) => i.shortcuts).flat(1); -export const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { +export const ShortcutsModal: FC = (props) => { + const { isOpen, onClose } = props; + // states const [query, setQuery] = useState(""); + // computed const filteredShortcuts = allShortcuts.filter((shortcut) => shortcut.description.toLowerCase().includes(query.trim().toLowerCase()) || query === "" ? true : false ); @@ -54,10 +57,10 @@ export const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { }, [isOpen]); return ( - - + + = ({ isOpen, setIsOpen }) => {
= ({ isOpen, setIsOpen }) => { > Keyboard Shortcuts - diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index 188ddfa30..221b6ffcc 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -33,18 +33,20 @@ type FormInput = { type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; + onClose: () => void; user: IUser | undefined; }; const issueService = new IssueService(); -export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user }) => { - const [query, setQuery] = useState(""); - +export const BulkDeleteIssuesModal: React.FC = (props) => { + const { isOpen, onClose, user } = props; + // router const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; - + // states + const [query, setQuery] = useState(""); + // fetching project issues. const { data: issues } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, workspaceSlug && projectId ? () => issueService.getIssues(workspaceSlug as string, projectId as string) : null @@ -68,9 +70,9 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user }); const handleClose = () => { - setIsOpen(false); setQuery(""); reset(); + onClose(); }; const handleDelete: SubmitHandler = async (data) => { diff --git a/web/components/cycles/modal.tsx b/web/components/cycles/modal.tsx index 9a92d267e..c6bb70851 100644 --- a/web/components/cycles/modal.tsx +++ b/web/components/cycles/modal.tsx @@ -1,5 +1,4 @@ import { Fragment } from "react"; -import { useRouter } from "next/router"; import { mutate } from "swr"; import { Dialog, Transition } from "@headlessui/react"; // services @@ -11,7 +10,7 @@ import { CycleForm } from "components/cycles"; // helper import { getDateRangeStatus } from "helpers/date-time.helper"; // types -import type { CycleDateCheckData, IUser, ICycle, IProject } from "types"; +import type { CycleDateCheckData, ICycle, IProject, IUser } from "types"; // fetch keys import { COMPLETED_CYCLES_LIST, @@ -27,23 +26,21 @@ type CycleModalProps = { isOpen: boolean; handleClose: () => void; data?: ICycle | null; - user: IUser | undefined; + workspaceSlug: string; + projectId: string; }; // services const cycleService = new CycleService(); -export const CreateUpdateCycleModal: React.FC = ({ isOpen, handleClose, data, user }) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; +export const CreateUpdateCycleModal: React.FC = (props) => { + const { isOpen, handleClose, data, workspaceSlug, projectId } = props; const { setToastAlert } = useToast(); const createCycle = async (payload: Partial) => { - if (!workspaceSlug || !projectId) return; - await cycleService - .createCycle(workspaceSlug.toString(), projectId.toString(), payload, user) + .createCycle(workspaceSlug.toString(), projectId.toString(), payload, {} as IUser) .then((res) => { switch (getDateRangeStatus(res.start_date, res.end_date)) { case "completed": @@ -91,10 +88,8 @@ export const CreateUpdateCycleModal: React.FC = ({ isOpen, hand }; const updateCycle = async (cycleId: string, payload: Partial) => { - if (!workspaceSlug || !projectId) return; - await cycleService - .updateCycle(workspaceSlug.toString(), projectId.toString(), cycleId, payload, user) + .updateCycle(workspaceSlug.toString(), projectId.toString(), cycleId, payload, {} as IUser) .then((res) => { switch (getDateRangeStatus(data?.start_date, data?.end_date)) { case "completed": @@ -177,7 +172,6 @@ export const CreateUpdateCycleModal: React.FC = ({ isOpen, hand if (isDateValid) { if (data) await updateCycle(data.id, payload); else await createCycle(payload); - handleClose(); } else setToastAlert({ diff --git a/web/components/cycles/select.tsx b/web/components/cycles/select.tsx index 966106573..ca7eb80de 100644 --- a/web/components/cycles/select.tsx +++ b/web/components/cycles/select.tsx @@ -50,7 +50,14 @@ export const CycleSelect: React.FC = ({ projectId, value, return ( <> - + {workspaceSlug && projectId && ( + + )} {({ open }) => ( <> diff --git a/web/components/headers/project-views.tsx b/web/components/headers/project-views.tsx index 003de9f9f..e72b97c47 100644 --- a/web/components/headers/project-views.tsx +++ b/web/components/headers/project-views.tsx @@ -19,13 +19,20 @@ export const ProjectViewsHeader: FC = (props) => { const { title } = props; // router const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; // states const [createViewModal, setCreateViewModal] = useState(false); return ( <> - setCreateViewModal(false)} /> + {workspaceSlug && projectId && ( + setCreateViewModal(false)} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} + /> + )}
diff --git a/web/components/modules/modal.tsx b/web/components/modules/modal.tsx index 73cd3a229..5ee8db3ac 100644 --- a/web/components/modules/modal.tsx +++ b/web/components/modules/modal.tsx @@ -1,5 +1,4 @@ import { Fragment } from "react"; -import { useRouter } from "next/router"; import { mutate } from "swr"; import { useForm } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; @@ -16,9 +15,10 @@ import { MODULE_LIST } from "constants/fetch-keys"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; + onClose: () => void; data?: IModule; - user: IUser | undefined; + workspaceSlug: string; + projectId: string; }; const defaultValues: Partial = { @@ -31,15 +31,14 @@ const defaultValues: Partial = { const moduleService = new ModuleService(); -export const CreateUpdateModuleModal: React.FC = ({ isOpen, setIsOpen, data, user }) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; +export const CreateUpdateModuleModal: React.FC = (props) => { + const { isOpen, onClose, data, workspaceSlug, projectId } = props; const { setToastAlert } = useToast(); const handleClose = () => { - setIsOpen(false); reset(defaultValues); + onClose(); }; const { reset } = useForm({ @@ -48,7 +47,7 @@ export const CreateUpdateModuleModal: React.FC = ({ isOpen, setIsOpen, da const createModule = async (payload: Partial) => { await moduleService - .createModule(workspaceSlug as string, projectId as string, payload, user) + .createModule(workspaceSlug as string, projectId as string, payload, {} as IUser) .then(() => { mutate(MODULE_LIST(projectId as string)); handleClose(); @@ -70,7 +69,7 @@ export const CreateUpdateModuleModal: React.FC = ({ isOpen, setIsOpen, da const updateModule = async (payload: Partial) => { await moduleService - .updateModule(workspaceSlug as string, projectId as string, data?.id ?? "", payload, user) + .updateModule(workspaceSlug as string, projectId as string, data?.id ?? "", payload, {} as IUser) .then((res) => { mutate( MODULE_LIST(projectId as string), diff --git a/web/components/pages/create-update-page-modal.tsx b/web/components/pages/create-update-page-modal.tsx index 8a17f2eab..49057dd72 100644 --- a/web/components/pages/create-update-page-modal.tsx +++ b/web/components/pages/create-update-page-modal.tsx @@ -22,14 +22,17 @@ type Props = { handleClose: () => void; data?: IPage | null; user: IUser | undefined; + workspaceSlug: string; + projectId: string; }; // services const pageService = new PageService(); -export const CreateUpdatePageModal: React.FC = ({ isOpen, handleClose, data, user }) => { +export const CreateUpdatePageModal: React.FC = (props) => { + const { isOpen, handleClose, data, user, workspaceSlug, projectId } = props; + // router const router = useRouter(); - const { workspaceSlug, projectId } = router.query; const { setToastAlert } = useToast(); diff --git a/web/components/pages/pages-view.tsx b/web/components/pages/pages-view.tsx index d3eb8af71..f441c4e00 100644 --- a/web/components/pages/pages-view.tsx +++ b/web/components/pages/pages-view.tsx @@ -38,14 +38,14 @@ const pageService = new PageService(); const projectService = new ProjectService(); export const PagesView: React.FC = ({ pages, viewType }) => { - const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false); - const [selectedPageToUpdate, setSelectedPageToUpdate] = useState(null); - - const [deletePageModal, setDeletePageModal] = useState(false); - const [selectedPageToDelete, setSelectedPageToDelete] = useState(null); - + // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; + // states + const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false); + const [selectedPageToUpdate, setSelectedPageToUpdate] = useState(null); + const [deletePageModal, setDeletePageModal] = useState(false); + const [selectedPageToDelete, setSelectedPageToDelete] = useState(null); const { user } = useUserAuth(); @@ -188,18 +188,25 @@ export const PagesView: React.FC = ({ pages, viewType }) => { return ( <> - setCreateUpdatePageModal(false)} - data={selectedPageToUpdate} - user={user} - /> - + {workspaceSlug && projectId && ( + <> + setCreateUpdatePageModal(false)} + data={selectedPageToUpdate} + user={user} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} + /> + + + )} + {pages ? (
{pages.length > 0 ? ( diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index 64c455551..88548f544 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -1,10 +1,11 @@ -import { useState, useEffect, Fragment } from "react"; +import { useState, useEffect, Fragment, FC } from "react"; import { useRouter } from "next/router"; import { useForm, Controller } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; // icons import { Users2, X } from "lucide-react"; // hooks +import { useMobxStore } from "lib/mobx/store-provider"; import useToast from "hooks/use-toast"; import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; import useWorkspaceMembers from "hooks/use-workspace-members"; @@ -17,16 +18,15 @@ import EmojiIconPicker from "components/emoji-icon-picker"; // helpers import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; // types -import { IUser, IProject } from "types"; +import { IProject } from "types"; // constants import { NETWORK_CHOICES } from "constants/project"; -import { useMobxStore } from "lib/mobx/store-provider"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; + onClose: () => void; setToFavorite?: boolean; - user: IUser | undefined; + workspaceSlug: string; }; const defaultValues: Partial = { @@ -40,26 +40,27 @@ const defaultValues: Partial = { project_lead: null, }; -const IsGuestCondition: React.FC<{ - setIsOpen: React.Dispatch>; -}> = ({ setIsOpen }) => { +interface IIsGuestCondition { + onClose: () => void; +} + +const IsGuestCondition: FC = ({ onClose }) => { const { setToastAlert } = useToast(); useEffect(() => { - setIsOpen(false); - + onClose(); setToastAlert({ title: "Error", type: "error", message: "You don't have permission to create project.", }); - }, [setIsOpen, setToastAlert]); + }, [onClose, setToastAlert]); return null; }; export const CreateProjectModal: React.FC = (props) => { - const { isOpen, setIsOpen, setToFavorite = false } = props; + const { isOpen, onClose, setToFavorite = false, workspaceSlug } = props; // store const { project: projectStore } = useMobxStore(); // states @@ -67,9 +68,6 @@ export const CreateProjectModal: React.FC = (props) => { const { setToastAlert } = useToast(); - const router = useRouter(); - const { workspaceSlug } = router.query; - const { memberDetails } = useWorkspaceMyMembership(); const { workspaceMembers } = useWorkspaceMembers(workspaceSlug?.toString() ?? ""); @@ -86,7 +84,7 @@ export const CreateProjectModal: React.FC = (props) => { }); const handleClose = () => { - setIsOpen(false); + onClose(); setIsChangeInIdentifierRequired(true); reset(defaultValues); }; @@ -172,7 +170,7 @@ export const CreateProjectModal: React.FC = (props) => { const currentNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network")); - if (memberDetails && isOpen) if (memberDetails.role <= 10) return ; + if (memberDetails && isOpen) if (memberDetails.role <= 10) return ; return ( diff --git a/web/components/project/sidebar-list.tsx b/web/components/project/sidebar-list.tsx index 93d7dcc55..2c33ca1b4 100644 --- a/web/components/project/sidebar-list.tsx +++ b/web/components/project/sidebar-list.tsx @@ -108,12 +108,16 @@ export const ProjectSidebarList: FC = observer(() => { return ( <> - + {workspaceSlug && ( + { + setIsProjectModalOpen(false); + }} + setToFavorite={isFavoriteProjectCreate} + workspaceSlug={workspaceSlug.toString()} + /> + )}
void; preLoadedData?: Partial | null; + workspaceSlug: string; + projectId: string; }; -export const CreateUpdateProjectViewModal: React.FC = observer((props) => { - const { data, isOpen, onClose, preLoadedData } = props; - - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - +export const CreateUpdateProjectViewModal: FC = observer((props) => { + const { data, isOpen, onClose, preLoadedData, workspaceSlug, projectId } = props; + // store const { projectViews: projectViewsStore } = useMobxStore(); - + // hooks const { setToastAlert } = useToast(); const handleClose = () => { onClose(); }; - const createView = async (formData: IProjectView) => { - if (!workspaceSlug || !projectId) return; - - const payload = { - ...formData, - }; - + const createView = async (payload: IProjectView) => { await projectViewsStore - .createView(workspaceSlug.toString(), projectId.toString(), payload) + .createView(workspaceSlug, projectId, payload) .then(() => handleClose()) .catch(() => setToastAlert({ @@ -52,15 +42,9 @@ export const CreateUpdateProjectViewModal: React.FC = observer((props) => ); }; - const updateView = async (formData: IProjectView) => { - if (!workspaceSlug || !projectId) return; - - const payload = { - ...formData, - }; - + const updateView = async (payload: IProjectView) => { await projectViewsStore - .updateView(workspaceSlug.toString(), projectId.toString(), data?.id as string, payload) + .updateView(workspaceSlug, projectId, data?.id as string, payload) .then(() => handleClose()) .catch(() => setToastAlert({ @@ -77,10 +61,10 @@ export const CreateUpdateProjectViewModal: React.FC = observer((props) => }; return ( - + = observer((props) =>
= (props) => { return ( <> - {/* */} +
diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index 816318ceb..5bac70249 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -66,12 +66,18 @@ const ProjectModules: NextPage = () => { } > - + {workspaceSlug && projectId && ( + { + setCreateUpdateModule(false); + }} + data={selectedModule} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} + /> + )} + {modules ? ( modules.length > 0 ? ( <> diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index c62042493..755237361 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -51,12 +51,11 @@ const tabsList = ["Recent", "All", "Favorites", "Created by me", "Created by oth const projectService = new ProjectService(); const ProjectPages: NextPage = () => { - const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false); - - const [viewType, setViewType] = useState("list"); - const router = useRouter(); const { workspaceSlug, projectId } = router.query; + // states + const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false); + const [viewType, setViewType] = useState("list"); const { user } = useUserAuth(); @@ -87,11 +86,16 @@ const ProjectPages: NextPage = () => { return ( <> - setCreateUpdatePageModal(false)} - user={user} - /> + {workspaceSlug && projectId && ( + setCreateUpdatePageModal(false)} + user={user} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId.toString()} + /> + )} + router.back()}> diff --git a/web/store/command-palette.store.ts b/web/store/command-palette.store.ts new file mode 100644 index 000000000..19c06e2e4 --- /dev/null +++ b/web/store/command-palette.store.ts @@ -0,0 +1,161 @@ +import { observable, action, makeObservable } from "mobx"; +// types +import { RootStore } from "./root"; +// services +import { ProjectService } from "services/project"; +import { PageService } from "services/page.service"; + +export interface ICommandPaletteStore { + isCommandPaletteOpen: boolean; + isShortcutModalOpen: boolean; + isCreateProjectModalOpen: boolean; + isCreateCycleModalOpen: boolean; + isCreateModuleModalOpen: boolean; + isCreateViewModalOpen: boolean; + isCreatePageModalOpen: boolean; + isCreateIssueModalOpen: boolean; + isDeleteIssueModalOpen: boolean; + isBulkDeleteIssueModalOpen: boolean; + + toggleCommandPaletteModal: (value?: boolean) => void; + toggleShortcutModal: (value?: boolean) => void; + toggleCreateProjectModal: (value?: boolean) => void; + toggleCreateCycleModal: (value?: boolean) => void; + toggleCreateViewModal: (value?: boolean) => void; + toggleCreatePageModal: (value?: boolean) => void; + toggleCreateIssueModal: (value?: boolean) => void; + toggleCreateModuleModal: (value?: boolean) => void; + toggleDeleteIssueModal: (value?: boolean) => void; + toggleBulkDeleteIssueModal: (value?: boolean) => void; +} + +class CommandPaletteStore implements ICommandPaletteStore { + isCommandPaletteOpen: boolean = false; + isShortcutModalOpen: boolean = false; + isCreateProjectModalOpen: boolean = false; + isCreateCycleModalOpen: boolean = false; + isCreateModuleModalOpen: boolean = false; + isCreateViewModalOpen: boolean = false; + isCreatePageModalOpen: boolean = false; + isCreateIssueModalOpen: boolean = false; + isDeleteIssueModalOpen: boolean = false; + isBulkDeleteIssueModalOpen: boolean = false; + // root store + rootStore; + // service + projectService; + pageService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observable + isCommandPaletteOpen: observable.ref, + isShortcutModalOpen: observable.ref, + isCreateProjectModalOpen: observable.ref, + isCreateCycleModalOpen: observable.ref, + isCreateModuleModalOpen: observable.ref, + isCreateViewModalOpen: observable.ref, + isCreatePageModalOpen: observable.ref, + isDeleteIssueModalOpen: observable.ref, + isCreateIssueModalOpen: observable.ref, + // computed + // projectPages: computed, + // action + toggleCommandPaletteModal: action, + toggleShortcutModal: action, + toggleCreateProjectModal: action, + toggleCreateCycleModal: action, + toggleCreateViewModal: action, + toggleCreatePageModal: action, + toggleCreateIssueModal: action, + toggleCreateModuleModal: action, + toggleBulkDeleteIssueModal: action, + }); + + this.rootStore = _rootStore; + this.projectService = new ProjectService(); + this.pageService = new PageService(); + } + + toggleCommandPaletteModal = (value?: boolean) => { + if (value) { + this.isCommandPaletteOpen = value; + } else { + this.isCommandPaletteOpen = !this.isCommandPaletteOpen; + } + }; + + toggleShortcutModal = (value?: boolean) => { + if (value) { + this.isShortcutModalOpen = value; + } else { + this.isShortcutModalOpen = !this.isShortcutModalOpen; + } + }; + + toggleCreateProjectModal = (value?: boolean) => { + if (value) { + this.isCreateProjectModalOpen = value; + } else { + this.isCreateProjectModalOpen = !this.isCreateProjectModalOpen; + } + }; + + toggleCreateCycleModal = (value?: boolean) => { + if (value) { + this.isCreateCycleModalOpen = value; + } else { + this.isCreateCycleModalOpen = !this.isCreateCycleModalOpen; + } + }; + + toggleCreateViewModal = (value?: boolean) => { + if (value) { + this.isCreateViewModalOpen = value; + } else { + this.isCreateViewModalOpen = !this.isCreateViewModalOpen; + } + }; + + toggleCreatePageModal = (value?: boolean) => { + if (value) { + this.isCreatePageModalOpen = value; + } else { + this.isCreatePageModalOpen = !this.isCreatePageModalOpen; + } + }; + + toggleCreateIssueModal = (value?: boolean) => { + if (value) { + this.isCreateIssueModalOpen = value; + } else { + this.isCreateIssueModalOpen = !this.isCreateIssueModalOpen; + } + }; + + toggleDeleteIssueModal = (value?: boolean) => { + if (value) { + this.isDeleteIssueModalOpen = value; + } else { + this.isDeleteIssueModalOpen = !this.isDeleteIssueModalOpen; + } + }; + + toggleCreateModuleModal = (value?: boolean) => { + if (value) { + this.isCreateModuleModalOpen = value; + } else { + this.isCreateModuleModalOpen = !this.isCreateModuleModalOpen; + } + }; + + toggleBulkDeleteIssueModal = (value?: boolean) => { + if (value) { + this.isBulkDeleteIssueModalOpen = value; + } else { + this.isBulkDeleteIssueModalOpen = !this.isBulkDeleteIssueModalOpen; + } + }; +} + +export default CommandPaletteStore; diff --git a/web/store/root.ts b/web/store/root.ts index 803fa3894..4a830435a 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -1,7 +1,8 @@ import { enableStaticRendering } from "mobx-react-lite"; // store imports -import UserStore from "store/user.store"; -import ThemeStore from "store/theme.store"; +import CommandPaletteStore, { ICommandPaletteStore } from "./command-palette.store"; +import UserStore, { IUserStore } from "store/user.store"; +import ThemeStore, { IThemeStore } from "store/theme.store"; import { DraftIssuesStore, IIssueDetailStore, @@ -79,9 +80,10 @@ import { enableStaticRendering(typeof window === "undefined"); export class RootStore { - user; - theme; + user: IUserStore; + theme: IThemeStore; + commandPalette: ICommandPaletteStore; workspace: IWorkspaceStore; workspaceFilter: IWorkspaceFilterStore; @@ -129,6 +131,7 @@ export class RootStore { inboxFilters: IInboxFiltersStore; constructor() { + this.commandPalette = new CommandPaletteStore(this); this.user = new UserStore(this); this.theme = new ThemeStore(this); diff --git a/web/store/theme.store.ts b/web/store/theme.store.ts index 66e851863..585e7f91d 100644 --- a/web/store/theme.store.ts +++ b/web/store/theme.store.ts @@ -3,7 +3,15 @@ import { action, observable, makeObservable } from "mobx"; // helper import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; -class ThemeStore { +export interface IThemeStore { + theme: string | null; + sidebarCollapsed: boolean | null; + + setSidebarCollapsed: (collapsed?: boolean) => void; + setTheme: (theme: any) => void; +} + +class ThemeStore implements IThemeStore { sidebarCollapsed: boolean | null = null; theme: string | null = null; // root store @@ -24,8 +32,8 @@ class ThemeStore { this.initialLoad(); } - setSidebarCollapsed(collapsed: boolean | null = null) { - if (collapsed === null) { + setSidebarCollapsed(collapsed?: boolean) { + if (!collapsed) { let _sidebarCollapsed: string | boolean | null = localStorage.getItem("app_sidebar_collapsed"); _sidebarCollapsed = _sidebarCollapsed ? (_sidebarCollapsed === "true" ? true : false) : false; this.sidebarCollapsed = _sidebarCollapsed; diff --git a/web/store/user.store.ts b/web/store/user.store.ts index 2e4e7d13b..30d4e8468 100644 --- a/web/store/user.store.ts +++ b/web/store/user.store.ts @@ -8,7 +8,7 @@ import { WorkspaceService } from "services/workspace.service"; import { IUser, IUserSettings } from "types/users"; import { IWorkspaceMember, IProjectMember } from "types"; -interface IUserStore { +export interface IUserStore { loader: boolean; currentUser: IUser | null; @@ -28,9 +28,11 @@ interface IUserStore { fetchUserWorkspaceInfo: (workspaceSlug: string) => Promise; fetchUserProjectInfo: (workspaceSlug: string, projectId: string) => Promise; + fetchUserDashboardInfo: (workspaceSlug: string, month: number) => Promise; updateTourCompleted: () => Promise; updateCurrentUser: (data: Partial) => Promise; + updateCurrentUserTheme: (theme: string) => Promise; } class UserStore implements IUserStore {