From 4d598fd6b61d504c7a381c1f7f4702e02b9ea097 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Sat, 4 Mar 2023 17:47:03 +0530 Subject: [PATCH 1/6] style: dropdowns, feat: favorite projects in sidebar --- .../components/core/board-view/all-boards.tsx | 76 +++---- .../core/board-view/single-board.tsx | 10 +- .../components/core/multi-level-select.tsx | 8 +- .../core/sidebar/single-progress-stats.tsx | 8 +- .../components/emoji-icon-picker/index.tsx | 20 +- apps/app/components/issues/form.tsx | 3 +- .../app/components/issues/select/assignee.tsx | 26 +-- apps/app/components/issues/select/date.tsx | 8 +- apps/app/components/issues/select/label.tsx | 38 ++-- .../app/components/issues/select/priority.tsx | 97 +++------ apps/app/components/issues/select/project.tsx | 103 +++------ apps/app/components/issues/select/state.tsx | 28 +-- .../components/labels/labels-list-modal.tsx | 4 +- .../components/labels/single-label-group.tsx | 52 ++--- .../modules/select/select-status.tsx | 58 +++-- .../project/create-project-modal.tsx | 3 +- apps/app/components/project/sidebar-list.tsx | 206 ++++++++++++++---- .../project/single-project-card.tsx | 22 +- apps/app/components/sidebar/projects-list.tsx | 193 ---------------- .../components/sidebar/workspace-options.tsx | 74 ------- .../components/states/create-state-modal.tsx | 34 +-- apps/app/components/ui/custom-listbox.tsx | 133 ----------- apps/app/components/ui/custom-menu.tsx | 104 ++++----- apps/app/components/ui/custom-select.tsx | 23 +- apps/app/components/ui/empty-space.tsx | 2 +- apps/app/components/ui/index.ts | 2 - apps/app/components/ui/select.tsx | 60 ----- .../app/components/workspace/help-section.tsx | 4 +- .../send-workspace-invitation-modal.tsx | 58 +++-- .../components/workspace/sidebar-dropdown.tsx | 4 +- .../app/components/workspace/sidebar-menu.tsx | 2 +- apps/app/constants/fetch-keys.ts | 5 +- apps/app/layouts/app-layout/app-header.tsx | 2 +- apps/app/layouts/app-layout/app-sidebar.tsx | 2 +- apps/app/layouts/app-layout/index.tsx | 2 +- .../projects/[projectId]/settings/control.tsx | 10 +- .../[projectId]/settings/features.tsx | 4 +- .../projects/[projectId]/settings/index.tsx | 2 +- .../[projectId]/settings/integrations.tsx | 4 +- .../projects/[projectId]/settings/members.tsx | 2 +- .../projects/[projectId]/settings/states.tsx | 2 +- apps/app/pages/invitations.tsx | 5 +- apps/app/services/project.service.ts | 9 + apps/app/types/projects.d.ts | 12 + 44 files changed, 574 insertions(+), 950 deletions(-) delete mode 100644 apps/app/components/sidebar/projects-list.tsx delete mode 100644 apps/app/components/sidebar/workspace-options.tsx delete mode 100644 apps/app/components/ui/custom-listbox.tsx delete mode 100644 apps/app/components/ui/select.tsx diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 9a1b96b2a..1b3c31716 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -37,49 +37,45 @@ export const AllBoards: React.FC = ({ return ( <> {groupedByIssues ? ( -
-
-
-
- {Object.keys(groupedByIssues).map((singleGroup, index) => { - const currentState = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup) - : null; +
+
+ {Object.keys(groupedByIssues).map((singleGroup, index) => { + const currentState = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup) + : null; - const stateId = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup)?.id ?? null - : null; + const stateId = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.id ?? null + : null; - const bgColor = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup)?.color - : "#000000"; + const bgColor = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.color + : "#000000"; - return ( - addIssueToState(singleGroup, stateId)} - handleDeleteIssue={handleDeleteIssue} - openIssuesListModal={openIssuesListModal ?? null} - orderBy={orderBy} - handleTrashBox={handleTrashBox} - removeIssue={removeIssue} - userAuth={userAuth} - /> - ); - })} -
-
+ return ( + addIssueToState(singleGroup, stateId)} + handleDeleteIssue={handleDeleteIssue} + openIssuesListModal={openIssuesListModal ?? null} + orderBy={orderBy} + handleTrashBox={handleTrashBox} + removeIssue={removeIssue} + userAuth={userAuth} + /> + ); + })}
) : ( diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx index 4aafda62a..8ed5f573a 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -91,7 +91,7 @@ export const SingleBoard: React.FC = ({ {(provided, snapshot) => (
= ({
This board is ordered by {replaceUnderscoreIfSnakeCase(orderBy ?? "")}
@@ -153,7 +153,7 @@ export const SingleBoard: React.FC = ({ {type === "issue" ? (
)} -
-

All Emojis

+
+

All Emojis

{emojis.map((emoji) => (
+ } + onChange={onChange} + > + {PRIORITIES.map((priority) => ( + +
+
+ {getPriorityIcon(priority)} + {priority ?? "None"} +
+
+
+ ))} + ); diff --git a/apps/app/components/issues/select/project.tsx b/apps/app/components/issues/select/project.tsx index 39b6b470d..1e09d0a35 100644 --- a/apps/app/components/issues/select/project.tsx +++ b/apps/app/components/issues/select/project.tsx @@ -1,11 +1,9 @@ -import { FC, Fragment } from "react"; - import { useRouter } from "next/router"; import useSWR from "swr"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; +// ui +import { CustomSelect } from "components/ui"; // icons import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; // services @@ -19,7 +17,7 @@ export interface IssueProjectSelectProps { setActiveProject: React.Dispatch>; } -export const IssueProjectSelect: FC = ({ +export const IssueProjectSelect: React.FC = ({ value, onChange, setActiveProject, @@ -34,71 +32,34 @@ export const IssueProjectSelect: FC = ({ ); return ( - <> - { - onChange(val); - setActiveProject(val); - }} - > - {({ open }) => ( - <> -
- - - - {projects?.find((i) => i.id === value)?.identifier ?? "Project"} - - - - - -
- {projects ? ( - projects.length > 0 ? ( - projects.map((project) => ( - - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } cursor-pointer select-none p-2 text-gray-900` - } - value={project.id} - > - {({ selected }) => ( - <> - - {project.name} - - - )} - - )) - ) : ( -

No projects found!

- ) - ) : ( -
Loading...
- )} -
-
-
-
- - )} -
- + + + + {projects?.find((i) => i.id === value)?.identifier ?? "Project"} + + + } + onChange={(val: string) => { + onChange(val); + setActiveProject(val); + }} + > + {projects ? ( + projects.length > 0 ? ( + projects.map((project) => ( + + <>{project.name} + + )) + ) : ( +

No projects found!

+ ) + ) : ( +
Loading...
+ )} +
); }; diff --git a/apps/app/components/issues/select/state.tsx b/apps/app/components/issues/select/state.tsx index 41f498ec0..a14d384a2 100644 --- a/apps/app/components/issues/select/state.tsx +++ b/apps/app/components/issues/select/state.tsx @@ -67,21 +67,21 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, <> - `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + `flex cursor-pointer items-center rounded-md border text-xs shadow-sm duration-200 ${ - open ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " : "hover:bg-theme/5" + open ? "border-theme bg-theme/5 outline-none ring-1 ring-theme " : "hover:bg-theme/5" }` } > {value && value !== "" ? ( - + {currentOption && currentOption.group ? getStateGroupIcon(currentOption.group, "16", "16", currentOption.color) : ""} {currentOption?.display} ) : ( - + {currentOption?.display || "State"} @@ -99,10 +99,10 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, leaveTo="opacity-0 translate-y-1" > -
+
= ({ setIsOpen, value, onChange, > {({ selected, active }) => states && ( -
-
+
+
{getStateGroupIcon(option.group, "16", "16", option.color)} {option.display}
-
+
@@ -142,17 +142,17 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, )) ) : ( -

No states found

+

No states found

) ) : ( -

Loading...

+

Loading...

)} -
- )} - - ) : ( -
- -
- - - - -
-
- - - - -
-
+ ); + })}
)} +
+ {!sidebarCollapse &&
Projects
} + {projects ? ( + <> + {normalProjects.length > 0 ? ( + normalProjects.map((project) => ( + + {({ open }) => ( + <> + +
+ {project.icon ? ( + + {String.fromCodePoint(parseInt(project.icon))} + + ) : ( + + {project?.name.charAt(0)} + + )} + + {!sidebarCollapse && ( +

+ {truncateText(project?.name, 20)} +

+ )} +
+ +
+ {!sidebarCollapse && ( + + handleCopyText(project.id)}> + Copy project link + + + )} + {!sidebarCollapse && ( + + + + )} +
+
+ + + + {navigation(workspaceSlug as string, project?.id).map((item) => { + if (item.name === "Cycles" && !project.cycle_view) return; + if (item.name === "Modules" && !project.module_view) return; + + return ( + + +
+
+ {!sidebarCollapse && item.name} +
+ + ); + })} +
+
+ + )} +
+ )) + ) : ( +
+ {!sidebarCollapse && ( +

You don{"'"}t have any project yet

+ )} + +
+ )} + + ) : ( +
+ +
+ + + + +
+
+ + + + +
+
+
+ )} +
); diff --git a/apps/app/components/project/single-project-card.tsx b/apps/app/components/project/single-project-card.tsx index b29c9e8a9..99ee69793 100644 --- a/apps/app/components/project/single-project-card.tsx +++ b/apps/app/components/project/single-project-card.tsx @@ -20,9 +20,9 @@ import { StarIcon } from "@heroicons/react/20/solid"; import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import { copyTextToClipboard, truncateText } from "helpers/string.helper"; // types -import type { IProject } from "types"; +import type { IFavoriteProject, IProject } from "types"; // fetch-keys -import { PROJECTS_LIST } from "constants/fetch-keys"; +import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST } from "constants/fetch-keys"; export type ProjectCardProps = { project: IProject; @@ -63,6 +63,7 @@ export const SingleProjectCard: React.FC = ({ })), false ); + mutate(FAVORITE_PROJECTS_LIST(workspaceSlug as string)); setToastAlert({ type: "success", @@ -94,6 +95,11 @@ export const SingleProjectCard: React.FC = ({ })), false ); + mutate( + FAVORITE_PROJECTS_LIST(workspaceSlug as string), + (prevData) => (prevData ?? []).filter((p) => p.project !== project.id), + false + ); setToastAlert({ type: "success", @@ -126,7 +132,7 @@ export const SingleProjectCard: React.FC = ({ return ( <> {members ? ( -
+
@@ -149,16 +155,16 @@ export const SingleProjectCard: React.FC = ({ e.stopPropagation(); setToJoinProject(project.id); }} - className="flex cursor-pointer items-center gap-1 bg-green-600 px-2 py-1 rounded text-xs" + className="flex cursor-pointer items-center gap-1 rounded bg-green-600 px-2 py-1 text-xs" > Select to Join ) : ( - Member + Member )} {project.is_favourite && ( - + )} @@ -166,7 +172,7 @@ export const SingleProjectCard: React.FC = ({
-
+
@@ -180,7 +186,7 @@ export const SingleProjectCard: React.FC = ({

{truncateText(project.description ?? "", 100)}

-
+
{ - name: string; - href: string; - icon: any; - }[]; - sidebarCollapse: boolean; -}; - -const ProjectsList: React.FC = ({ navigation, sidebarCollapse }) => { - const [isCreateProjectModal, setCreateProjectModal] = useState(false); - - const router = useRouter(); - - const { workspaceSlug, projectId } = router.query; - - const { setToastAlert } = useToast(); - - const { data: projects } = useSWR( - workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null, - () => (workspaceSlug ? projectService.getProjects(workspaceSlug as string) : null) - ); - - return ( - <> - -
- {projects ? ( - <> - {projects.length > 0 ? ( - projects.map((project) => ( - - {({ open }) => ( - <> -
- - {project.icon ? ( - - {String.fromCodePoint(parseInt(project.icon))} - - ) : ( - - {project?.name.charAt(0)} - - )} - - {!sidebarCollapse && ( - - - {project?.name} - - - - - - )} - - {!sidebarCollapse && ( - - - copyTextToClipboard( - `https://app.plane.so/${workspaceSlug}/projects/${project?.id}/issues/` - ).then(() => { - setToastAlert({ - title: "Link Copied", - message: "Link copied to clipboard", - type: "success", - }); - }) - } - > - Copy link - - - )} -
- - - {navigation(workspaceSlug as string, project?.id).map((item) => ( - - - - - ))} - - - - )} -
- )) - ) : ( -
- {!sidebarCollapse && ( -

You don{"'"}t have any project yet

- )} - -
- )} - - ) : ( -
- -
- - - - -
-
- - - - -
-
-
- )} -
- - ); -}; - -export default ProjectsList; diff --git a/apps/app/components/sidebar/workspace-options.tsx b/apps/app/components/sidebar/workspace-options.tsx deleted file mode 100644 index 8dde59f2a..000000000 --- a/apps/app/components/sidebar/workspace-options.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useRouter } from "next/router"; -import Link from "next/link"; - -import { - ClipboardDocumentListIcon, - Cog6ToothIcon, - HomeIcon, - RectangleStackIcon, -} from "@heroicons/react/24/outline"; - -type Props = { - sidebarCollapse: boolean; -}; - -const workspaceLinks = (workspaceSlug: string) => [ - { - icon: HomeIcon, - name: "Home", - href: `/${workspaceSlug}`, - }, - { - icon: ClipboardDocumentListIcon, - name: "Projects", - href: `/${workspaceSlug}/projects`, - }, - { - icon: RectangleStackIcon, - name: "My Issues", - href: `/${workspaceSlug}/me/my-issues`, - }, - { - icon: Cog6ToothIcon, - name: "Settings", - href: `/${workspaceSlug}/settings`, - }, -]; - -const WorkspaceOptions: React.FC = ({ sidebarCollapse }) => { - const router = useRouter(); - - const { - query: { workspaceSlug }, - } = router; - - return ( -
-
- {workspaceLinks(workspaceSlug as string).map((link, index) => ( - - - - - ))} -
-
- ); -}; - -export default WorkspaceOptions; diff --git a/apps/app/components/states/create-state-modal.tsx b/apps/app/components/states/create-state-modal.tsx index c3bdd5526..69208873a 100644 --- a/apps/app/components/states/create-state-modal.tsx +++ b/apps/app/components/states/create-state-modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { useRouter } from "next/router"; @@ -13,7 +13,7 @@ import { Dialog, Popover, Transition } from "@headlessui/react"; // services import stateService from "services/state.service"; // ui -import { Button, Input, Select, TextArea } from "components/ui"; +import { Button, CustomSelect, Input, TextArea } from "components/ui"; // icons import { ChevronDownIcon } from "@heroicons/react/24/outline"; // types @@ -129,19 +129,25 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo />
- - {options.map((option, index) => ( - - ))} - - {error?.message &&
{error.message}
} - -); diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index 5a5e3a600..53cc1e461 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -56,7 +56,7 @@ export const WorkspaceHelpSection: FC = (props) => { return (
@@ -132,7 +132,7 @@ export const WorkspaceHelpSection: FC = (props) => { {name} diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index 0446e201c..81ebacc69 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -1,12 +1,12 @@ import React from "react"; import { mutate } from "swr"; -import { useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; // headless import { Dialog, Transition } from "@headlessui/react"; // services import workspaceService from "services/workspace.service"; // ui -import { Button, Input, Select } from "components/ui"; +import { Button, CustomSelect, Input } from "components/ui"; // hooks import useToast from "hooks/use-toast"; // types @@ -37,6 +37,7 @@ const SendWorkspaceInvitationModal: React.FC = ({ const { setToastAlert } = useToast(); const { + control, register, formState: { errors, isSubmitting }, handleSubmit, @@ -44,7 +45,6 @@ const SendWorkspaceInvitationModal: React.FC = ({ } = useForm({ defaultValues, reValidateMode: "onChange", - mode: "all", }); const handleClose = () => { @@ -98,13 +98,13 @@ const SendWorkspaceInvitationModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
- - Members - -
+
+ + Members +

Invite members to work on your workspace.

@@ -129,34 +129,30 @@ const SendWorkspaceInvitationModal: React.FC = ({ />
-