From ada68a9bf7297ec610b19c25f8d4bbd0bb68bf31 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Tue, 13 Dec 2022 12:47:35 +0530 Subject: [PATCH 1/6] refractor: removed redirection logic from AppLayout --- apps/app/layouts/AppLayout.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/app/layouts/AppLayout.tsx b/apps/app/layouts/AppLayout.tsx index dbdddf0a7..e5135a605 100644 --- a/apps/app/layouts/AppLayout.tsx +++ b/apps/app/layouts/AppLayout.tsx @@ -1,9 +1,4 @@ -// react -import React, { useEffect, useState } from "react"; -// next -import { useRouter } from "next/router"; -// hooks -import useUser from "lib/hooks/useUser"; +import React, { useState } from "react"; // layouts import Container from "layouts/Container"; import Sidebar from "layouts/Navbar/Sidebar"; @@ -15,14 +10,6 @@ import type { Props } from "./types"; const AppLayout: React.FC = ({ meta, children, noPadding = false, bg = "primary" }) => { const [isOpen, setIsOpen] = useState(false); - const router = useRouter(); - - const { user, isUserLoading } = useUser(); - - useEffect(() => { - if (!isUserLoading && (!user || user === null)) router.push("/signin"); - }, [isUserLoading, user, router]); - return ( From b5a33d8f4d3d50c58b2a4b55619be29f682a759a Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Tue, 13 Dec 2022 14:18:49 +0530 Subject: [PATCH 2/6] fix: mutation in issue comments --- .../issue-detail/comment/IssueCommentCard.tsx | 19 ------------------ .../comment/IssueCommentSection.tsx | 20 ++++++++++++------- apps/app/constants/fetch-keys.ts | 2 +- .../projects/[projectId]/issues/[issueId].tsx | 2 +- 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx b/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx index e911ae306..90c3b0bed 100644 --- a/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx +++ b/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx @@ -1,16 +1,12 @@ import React, { useEffect, useState } from "react"; // next import Image from "next/image"; -// swr -import { mutate } from "swr"; // headless ui import { Menu } from "@headlessui/react"; // react hook form import { useForm } from "react-hook-form"; // hooks import useUser from "lib/hooks/useUser"; -// fetch keys -import { PROJECT_ISSUES_COMMENTS } from "constants/fetch-keys"; // common import { timeAgo } from "constants/common"; // ui @@ -42,16 +38,6 @@ const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion const onEnter = (formData: IIssueComment) => { if (isSubmitting) return; - mutate( - PROJECT_ISSUES_COMMENTS, - (prevData) => { - const newData = prevData ?? []; - const index = newData.findIndex((comment) => comment.id === formData.id); - newData[index] = formData; - return [...newData]; - }, - false - ); setIsEditing(false); onSubmit(formData); }; @@ -155,11 +141,6 @@ const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion className="w-full text-left py-2 pl-2" type="button" onClick={() => { - mutate( - PROJECT_ISSUES_COMMENTS, - (prevData) => (prevData ?? []).filter((c) => c.id !== comment.id), - false - ); handleCommentDeletion(comment.id); }} > diff --git a/apps/app/components/project/issues/issue-detail/comment/IssueCommentSection.tsx b/apps/app/components/project/issues/issue-detail/comment/IssueCommentSection.tsx index fe07421f1..d70196b28 100644 --- a/apps/app/components/project/issues/issue-detail/comment/IssueCommentSection.tsx +++ b/apps/app/components/project/issues/issue-detail/comment/IssueCommentSection.tsx @@ -13,8 +13,6 @@ import CommentCard from "components/project/issues/issue-detail/comment/IssueCom import { TextArea, Button, Spinner } from "ui"; // types import type { IIssueComment } from "types"; -// icons -import UploadingIcon from "public/animated-icons/uploading.json"; type Props = { comments?: IIssueComment[]; @@ -41,11 +39,10 @@ const IssueCommentSection: React.FC = ({ comments, issueId, projectId, wo .createIssueComment(workspaceSlug, projectId, issueId, formData) .then((response) => { console.log(response); - mutate( - PROJECT_ISSUES_COMMENTS, - (prevData) => [...(prevData ?? []), response], - false - ); + mutate(PROJECT_ISSUES_COMMENTS(issueId), (prevData) => [ + response, + ...(prevData ?? []), + ]); reset(defaultValues); }) .catch((error) => { @@ -58,6 +55,12 @@ const IssueCommentSection: React.FC = ({ comments, issueId, projectId, wo .patchIssueComment(workspaceSlug, projectId, issueId, comment.id, comment) .then((response) => { console.log(response); + mutate(PROJECT_ISSUES_COMMENTS(issueId), (prevData) => { + const newData = prevData ?? []; + const index = newData.findIndex((comment) => comment.id === response.id); + newData[index] = response; + return [...newData]; + }); }); }; @@ -65,6 +68,9 @@ const IssueCommentSection: React.FC = ({ comments, issueId, projectId, wo await issuesServices .deleteIssueComment(workspaceSlug, projectId, issueId, commentId) .then((response) => { + mutate(PROJECT_ISSUES_COMMENTS(issueId), (prevData) => + (prevData ?? []).filter((c) => c.id !== commentId) + ); console.log(response); }); }; diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index 76a3807ae..781efe2fc 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -17,7 +17,7 @@ export const PROJECT_ISSUES_LIST = (workspaceSlug: string, projectId: string) => export const PROJECT_ISSUES_DETAILS = (issueId: string) => `PROJECT_ISSUES_DETAILS_${issueId}`; export const PROJECT_ISSUES_PROPERTIES = (projectId: string) => `PROJECT_ISSUES_PROPERTIES_${projectId}`; -export const PROJECT_ISSUES_COMMENTS = "PROJECT_ISSUES_COMMENTS"; +export const PROJECT_ISSUES_COMMENTS = (issueId: string) => `PROJECT_ISSUES_COMMENTS_${issueId}`; export const PROJECT_ISSUES_ACTIVITY = "PROJECT_ISSUES_ACTIVITY"; export const PROJECT_ISSUE_BY_STATE = (projectId: string) => `PROJECT_ISSUE_BY_STATE_${projectId}`; export const PROJECT_ISSUE_LABELS = (projectId: string) => `PROJECT_ISSUE_LABELS_${projectId}`; diff --git a/apps/app/pages/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/projects/[projectId]/issues/[issueId].tsx index 74dcc8dce..1f0a6ae96 100644 --- a/apps/app/pages/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/projects/[projectId]/issues/[issueId].tsx @@ -115,7 +115,7 @@ const IssueDetail: NextPage = () => { ); const { data: issueComments } = useSWR( - activeWorkspace && projectId && issueId ? PROJECT_ISSUES_COMMENTS : null, + activeWorkspace && projectId && issueId ? PROJECT_ISSUES_COMMENTS(issueId as string) : null, activeWorkspace && projectId && issueId ? () => issuesServices.getIssueComments( From c9b1a2590a0c487703264a4ddffd7ad65db06e37 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Tue, 13 Dec 2022 18:45:23 +0530 Subject: [PATCH 3/6] feat: create/update state according to group from project settings pages refractor: followed naming convension and made components easy to use --- ...eDeletion.tsx => confirm-state-delete.tsx} | 12 +- ...odal.tsx => create-update-state-modal.tsx} | 21 +- .../issues/CreateUpdateIssueModal/index.tsx | 2 +- .../project/settings/StatesSettings.tsx | 315 +++++++++++++++--- apps/app/constants/index.ts | 8 + 5 files changed, 296 insertions(+), 62 deletions(-) rename apps/app/components/project/issues/BoardView/state/{ConfirmStateDeletion.tsx => confirm-state-delete.tsx} (95%) rename apps/app/components/project/issues/BoardView/state/{CreateUpdateStateModal.tsx => create-update-state-modal.tsx} (91%) diff --git a/apps/app/components/project/issues/BoardView/state/ConfirmStateDeletion.tsx b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx similarity index 95% rename from apps/app/components/project/issues/BoardView/state/ConfirmStateDeletion.tsx rename to apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx index 448c50acc..d39962ca1 100644 --- a/apps/app/components/project/issues/BoardView/state/ConfirmStateDeletion.tsx +++ b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx @@ -18,11 +18,11 @@ import { Button } from "ui"; import type { IState } from "types"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; - data?: IState; + onClose: () => void; + data: IState | null; }; -const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { +const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); const { activeWorkspace } = useUser(); @@ -30,7 +30,7 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { const cancelButtonRef = useRef(null); const handleClose = () => { - setIsOpen(false); + onClose(); setIsDeleteLoading(false); }; @@ -53,10 +53,6 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { }); }; - useEffect(() => { - data && setIsOpen(true); - }, [data, setIsOpen]); - return ( = { name: "", description: "", color: "#000000", + group: "backlog", }; const CreateUpdateStateModal: React.FC = ({ isOpen, data, projectId, handleClose }) => { @@ -161,6 +164,22 @@ const CreateUpdateStateModal: React.FC = ({ isOpen, data, projectId, hand }} /> +
+ + + + +
+ ); +}; + +const StatesSettings: React.FC = ({ projectId }) => { + const [activeGroup, setActiveGroup] = useState(null); + const [selectedState, setSelectedState] = useState(null); + const [selectDeleteState, setSelectDeleteState] = useState(null); + + const { states, activeWorkspace } = useUser(); + + const groupedStates: { + [key: string]: Array; + } = groupBy(states ?? [], "group"); return ( <> - { - setSelectedState(undefined); - setIsCreateStateModal(false); - }} - projectId={projectId as string} - data={selectedState ? states?.find((state) => state.id === selectedState) : undefined} + state.id === selectDeleteState) ?? null} + onClose={() => setSelectDeleteState(null)} /> +

State

Manage the state of this project.

-
-
- {states?.map((state) => ( -
-
-
-

{addSpaceIfCamelCase(state.name)}

-
-
- -
+
+ {Object.keys(groupedStates).map((key) => ( +
+
+

{key} states

+
- ))} - -
+ {groupedStates[key]?.map((state) => + state.id !== selectedState ? ( +
+
+
+

{addSpaceIfCamelCase(state.name)}

+
+
+ + +
+
+ ) : ( + { + setActiveGroup(null); + setSelectedState(null); + }} + workspaceSlug={activeWorkspace?.slug} + data={states?.find((state) => state.id === selectedState) ?? null} + selectedGroup={key as keyof StateGroup} + /> + ) + )} + {key === activeGroup && ( + { + setActiveGroup(null); + setSelectedState(null); + }} + workspaceSlug={activeWorkspace?.slug} + data={null} + selectedGroup={key as keyof StateGroup} + /> + )} +
+ ))}
diff --git a/apps/app/constants/index.ts b/apps/app/constants/index.ts index 603652e9f..0e9a67788 100644 --- a/apps/app/constants/index.ts +++ b/apps/app/constants/index.ts @@ -8,3 +8,11 @@ export const ROLE = { }; export const NETWORK_CHOICES = { "0": "Secret", "2": "Public" }; + +export const GROUP_CHOICES = { + backlog: "Backlog", + unstarted: "Unstarted", + started: "Started", + completed: "Completed", + cancelled: "Cancelled", +}; From 53c3aec8b225c3836983aa7e630e27eb8f85bd82 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Wed, 14 Dec 2022 22:46:41 +0530 Subject: [PATCH 4/6] style: made each state group capsule --- .../state/create-update-state-inline.tsx | 184 +++++++++++ .../project/settings/StatesSettings.tsx | 291 ++++-------------- 2 files changed, 247 insertions(+), 228 deletions(-) create mode 100644 apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx diff --git a/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx b/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx new file mode 100644 index 000000000..84312a404 --- /dev/null +++ b/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx @@ -0,0 +1,184 @@ +import React, { useEffect } from "react"; +// swr +import { mutate } from "swr"; +// react hook form +import { useForm, Controller } from "react-hook-form"; +// react color +import { TwitterPicker } from "react-color"; +// headless +import { Popover, Transition } from "@headlessui/react"; +// constants +import { STATE_LIST } from "constants/fetch-keys"; +// services +import stateService from "lib/services/state.service"; +// ui +import { Button, Input } from "ui"; +// types +import type { IState } from "types"; + +type Props = { + workspaceSlug?: string; + projectId?: string; + data: IState | null; + onClose: () => void; + selectedGroup: StateGroup | null; +}; + +export type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null; + +export const CreateUpdateStateInline: React.FC = ({ + workspaceSlug, + projectId, + data, + onClose, + selectedGroup, +}) => { + const { + register, + handleSubmit, + formState: { errors }, + setError, + watch, + reset, + control, + } = useForm({ + defaultValues: { + name: "", + color: "#000000", + group: "backlog", + }, + }); + + const handleClose = () => { + onClose(); + reset({ name: "", color: "#000000", group: "backlog" }); + }; + + const onSubmit = async (formData: IState) => { + if (!workspaceSlug || !projectId) return; + const payload: IState = { + ...formData, + }; + if (!data) { + await stateService + .createState(workspaceSlug, projectId, { ...payload, group: selectedGroup }) + .then((res) => { + mutate(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false); + handleClose(); + }) + .catch((err) => { + Object.keys(err).map((key) => { + setError(key as keyof IState, { + message: err[key].join(", "), + }); + }); + }); + } else { + await stateService + .updateState(workspaceSlug, projectId, data.id, { + ...payload, + group: selectedGroup ?? "backlog", + }) + .then((res) => { + mutate( + STATE_LIST(projectId), + (prevData) => { + const newData = prevData?.map((item) => { + if (item.id === res.id) { + return res; + } + return item; + }); + return newData; + }, + false + ); + handleClose(); + }) + .catch((err) => { + Object.keys(err).map((key) => { + setError(key as keyof IState, { + message: err[key].join(", "), + }); + }); + }); + } + }; + + useEffect(() => { + if (data === null) return; + reset(data); + }, [data]); + + return ( +
+
+ + {({ open }) => ( + <> + + {watch("color") && watch("color") !== "" && ( + + )} + + + + + ( + onChange(value.hex)} /> + )} + /> + + + + )} + +
+ + + + +
+ ); +}; diff --git a/apps/app/components/project/settings/StatesSettings.tsx b/apps/app/components/project/settings/StatesSettings.tsx index f793046d4..cfb642c69 100644 --- a/apps/app/components/project/settings/StatesSettings.tsx +++ b/apps/app/components/project/settings/StatesSettings.tsx @@ -1,22 +1,12 @@ -import React, { useEffect, useState } from "react"; -// swr -import { mutate } from "swr"; -// react hook form -import { useForm, Controller } from "react-hook-form"; -// react color -import { TwitterPicker } from "react-color"; -// headless -import { Popover, Transition } from "@headlessui/react"; +import React, { useState } from "react"; // hooks import useUser from "lib/hooks/useUser"; -// constants -import { STATE_LIST } from "constants/fetch-keys"; -// services -import stateService from "lib/services/state.service"; // components +import { + StateGroup, + CreateUpdateStateInline, +} from "components/project/issues/BoardView/state/create-update-state-inline"; import ConfirmStateDeletion from "components/project/issues/BoardView/state/confirm-state-delete"; -// ui -import { Button, Input } from "ui"; // icons import { PencilSquareIcon, PlusIcon, TrashIcon } from "@heroicons/react/24/outline"; // constants @@ -28,173 +18,6 @@ type Props = { projectId: string | string[] | undefined; }; -type CreateUpdateStateProps = { - workspaceSlug?: string; - projectId?: string; - data: IState | null; - onClose: () => void; - selectedGroup: StateGroup | null; -}; - -type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null; - -const CreateUpdateState: React.FC = ({ - workspaceSlug, - projectId, - data, - onClose, - selectedGroup, -}) => { - const { - register, - handleSubmit, - formState: { errors }, - setError, - watch, - reset, - control, - } = useForm({ - defaultValues: { - name: "", - color: "#000000", - group: "backlog", - }, - }); - - const handleClose = () => { - onClose(); - reset({ name: "", color: "#000000", group: "backlog" }); - }; - - const onSubmit = async (formData: IState) => { - if (!workspaceSlug || !projectId) return; - const payload: IState = { - ...formData, - }; - if (!data) { - await stateService - .createState(workspaceSlug, projectId, { ...payload, group: selectedGroup }) - .then((res) => { - mutate(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false); - handleClose(); - }) - .catch((err) => { - Object.keys(err).map((key) => { - setError(key as keyof IState, { - message: err[key].join(", "), - }); - }); - }); - } else { - await stateService - .updateState(workspaceSlug, projectId, data.id, { - ...payload, - group: selectedGroup ?? "backlog", - }) - .then((res) => { - mutate( - STATE_LIST(projectId), - (prevData) => { - const newData = prevData?.map((item) => { - if (item.id === res.id) { - return res; - } - return item; - }); - return newData; - }, - false - ); - handleClose(); - }) - .catch((err) => { - Object.keys(err).map((key) => { - setError(key as keyof IState, { - message: err[key].join(", "), - }); - }); - }); - } - }; - - useEffect(() => { - if (data === null) return; - reset(data); - }, [data]); - - return ( -
-
- - {({ open }) => ( - <> - - {watch("color") && watch("color") !== "" && ( - - )} - - - - - ( - onChange(value.hex)} /> - )} - /> - - - - )} - -
- - - - -
- ); -}; - const StatesSettings: React.FC = ({ projectId }) => { const [activeGroup, setActiveGroup] = useState(null); const [selectedState, setSelectedState] = useState(null); @@ -221,64 +44,76 @@ const StatesSettings: React.FC = ({ projectId }) => {
{Object.keys(groupedStates).map((key) => ( -
-
+ +

{key} states

-
- {groupedStates[key]?.map((state) => - state.id !== selectedState ? ( -
-
+
+
+ {groupedStates[key]?.map((state) => + state.id !== selectedState ? (
-

{addSpaceIfCamelCase(state.name)}

-
-
- - -
-
- ) : ( - +
+
+

{addSpaceIfCamelCase(state.name)}

+
+
+ + +
+
+ ) : ( +
+ { + setActiveGroup(null); + setSelectedState(null); + }} + workspaceSlug={activeWorkspace?.slug} + data={states?.find((state) => state.id === selectedState) ?? null} + selectedGroup={key as keyof StateGroup} + /> +
+ ) + )} +
+ {key === activeGroup && ( + { setActiveGroup(null); setSelectedState(null); }} workspaceSlug={activeWorkspace?.slug} - data={states?.find((state) => state.id === selectedState) ?? null} + data={null} selectedGroup={key as keyof StateGroup} /> - ) - )} - {key === activeGroup && ( - { - setActiveGroup(null); - setSelectedState(null); - }} - workspaceSlug={activeWorkspace?.slug} - data={null} - selectedGroup={key as keyof StateGroup} - /> - )} -
+ )} +
+ ))}
From 41271a5e3fa3ef4053570944802be5b8bc74c1d9 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Thu, 15 Dec 2022 10:35:05 +0530 Subject: [PATCH 5/6] feat: can't delete state with issues --- .../BoardView/state/confirm-state-delete.tsx | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx index d39962ca1..ae1d2d4d1 100644 --- a/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx +++ b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx @@ -9,6 +9,8 @@ import stateServices from "lib/services/state.service"; import { STATE_LIST } from "constants/fetch-keys"; // hooks import useUser from "lib/hooks/useUser"; +// common +import { groupBy } from "constants/common"; // icons import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui @@ -25,7 +27,9 @@ type Props = { const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); - const { activeWorkspace } = useUser(); + const [issuesWithThisStateExist, setIssuesWithThisStateExist] = useState(true); + + const { activeWorkspace, issues } = useUser(); const cancelButtonRef = useRef(null); @@ -36,7 +40,7 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { const handleDeletion = async () => { setIsDeleteLoading(true); - if (!data || !activeWorkspace) return; + if (!data || !activeWorkspace || issuesWithThisStateExist) return; await stateServices .deleteState(activeWorkspace.slug, data.project, data.id) .then(() => { @@ -53,6 +57,12 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { }); }; + const groupedIssues = groupBy(issues?.results ?? [], "state"); + + useEffect(() => { + if (data) setIssuesWithThisStateExist(!!groupedIssues[data.id]); + }, [groupedIssues]); + return ( = ({ isOpen, onClose, data }) => { This action cannot be undone.

+
+ {issuesWithThisStateExist && ( +

+ There are issues with this state. Please move them to another state + before deleting this state. +

+ )} +
@@ -113,7 +131,7 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { type="button" onClick={handleDeletion} theme="danger" - disabled={isDeleteLoading} + disabled={isDeleteLoading || issuesWithThisStateExist} className="inline-flex sm:ml-3" > {isDeleteLoading ? "Deleting..." : "Delete"} From 676355d673c7f24862f54e1482e5b70ab1d0bf6d Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Fri, 16 Dec 2022 20:26:25 +0530 Subject: [PATCH 6/6] feat: project settings state, filter by cycle refractor: added types for cycle in IIssue, improved common function used for grouping, made custom hook for my issue filter --- .eslintrc.json => apps/app/.eslintrc.json | 0 .../components/command-palette/shortcuts.tsx | 131 ++++++++++------- .../BoardView/state/confirm-state-delete.tsx | 2 +- .../state/create-update-state-inline.tsx | 53 +++++-- .../project/issues/ListView/index.tsx | 3 + .../components/socialbuttons/google-login.tsx | 56 +++---- apps/app/constants/common.ts | 2 +- apps/app/constants/fetch-keys.ts | 2 + apps/app/contexts/theme.context.tsx | 110 +++++++++----- apps/app/lib/hooks/useMyIssueFilter.tsx | 105 ++++++++++++++ apps/app/lib/services/project.service.ts | 16 +- apps/app/lib/services/user.service.ts | 3 +- apps/app/lib/services/workspace.service.ts | 17 ++- apps/app/pages/me/my-issues.tsx | 137 +++++++++++++++--- .../projects/[projectId]/issues/index.tsx | 1 + apps/app/pages/workspace/settings.tsx | 9 +- apps/app/types/issues.d.ts | 17 ++- apps/app/types/users.d.ts | 8 + apps/app/types/workspace.d.ts | 7 +- yarn.lock | 7 +- 20 files changed, 532 insertions(+), 154 deletions(-) rename .eslintrc.json => apps/app/.eslintrc.json (100%) create mode 100644 apps/app/lib/hooks/useMyIssueFilter.tsx diff --git a/.eslintrc.json b/apps/app/.eslintrc.json similarity index 100% rename from .eslintrc.json rename to apps/app/.eslintrc.json diff --git a/apps/app/components/command-palette/shortcuts.tsx b/apps/app/components/command-palette/shortcuts.tsx index 91a8baab3..e4521b25a 100644 --- a/apps/app/components/command-palette/shortcuts.tsx +++ b/apps/app/components/command-palette/shortcuts.tsx @@ -1,15 +1,53 @@ -import React from "react"; +import React, { useState } from "react"; // headless ui import { Dialog, Transition } from "@headlessui/react"; // icons import { XMarkIcon } from "@heroicons/react/20/solid"; +// ui +import { Input } from "ui"; type Props = { isOpen: boolean; setIsOpen: React.Dispatch>; }; +const shortcuts = [ + { + title: "Navigation", + shortcuts: [ + { keys: "ctrl,/", description: "To open navigator" }, + { keys: "↑", description: "Move up" }, + { keys: "↓", description: "Move down" }, + { keys: "←", description: "Move left" }, + { keys: "→", description: "Move right" }, + { keys: "Enter", description: "Select" }, + { keys: "Esc", description: "Close" }, + ], + }, + { + title: "Common", + shortcuts: [ + { keys: "ctrl,p", description: "To create project" }, + { keys: "ctrl,i", description: "To create issue" }, + { keys: "ctrl,q", description: "To create cycle" }, + { keys: "ctrl,h", description: "To open shortcuts guide" }, + { + keys: "ctrl,alt,c", + description: "To copy issue url when on issue detail page.", + }, + ], + }, +]; + const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { + const [query, setQuery] = useState(""); + + const filteredShortcuts = shortcuts.filter((shortcut) => + shortcut.shortcuts.some((item) => item.description.includes(query.trim())) || query === "" + ? true + : false + ); + return ( @@ -39,7 +77,7 @@ const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => {
-
+
= ({ isOpen, setIsOpen }) => { -
- {[ - { - title: "Navigation", - shortcuts: [ - { keys: "ctrl,/", description: "To open navigator" }, - { keys: "↑", description: "Move up" }, - { keys: "↓", description: "Move down" }, - { keys: "←", description: "Move left" }, - { keys: "→", description: "Move right" }, - { keys: "Enter", description: "Select" }, - { keys: "Esc", description: "Close" }, - ], - }, - { - title: "Common", - shortcuts: [ - { keys: "ctrl,p", description: "To create project" }, - { keys: "ctrl,i", description: "To create issue" }, - { keys: "ctrl,q", description: "To create cycle" }, - { keys: "ctrl,h", description: "To open shortcuts guide" }, - { - keys: "ctrl,alt,c", - description: "To copy issue url when on issue detail page.", - }, - ], - }, - ].map(({ title, shortcuts }) => ( -
-

{title}

-
- {shortcuts.map(({ keys, description }, index) => ( -
-

{description}

-
- {keys.split(",").map((key, index) => ( - - - {key} - - {/* {index !== keys.split(",").length - 1 ? ( - + - ) : null} */} - - ))} +
+ setQuery(e.target.value)} + /> +
+
+ {filteredShortcuts.length > 0 ? ( + filteredShortcuts.map(({ title, shortcuts }) => ( +
+

{title}

+
+ {shortcuts.map(({ keys, description }, index) => ( +
+

{description}

+
+ {keys.split(",").map((key, index) => ( + + + {key} + + + ))} +
-
- ))} + ))} +
+ )) + ) : ( +
+

+ No shortcuts found for{" "} + + {`"`} + {query} + {`"`} + +

- ))} + )}
diff --git a/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx index ae1d2d4d1..8b884788c 100644 --- a/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx +++ b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx @@ -61,7 +61,7 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { useEffect(() => { if (data) setIssuesWithThisStateExist(!!groupedIssues[data.id]); - }, [groupedIssues]); + }, [groupedIssues, data]); return ( diff --git a/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx b/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx index 84312a404..10f186bd2 100644 --- a/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx +++ b/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx @@ -8,11 +8,12 @@ import { TwitterPicker } from "react-color"; // headless import { Popover, Transition } from "@headlessui/react"; // constants +import { GROUP_CHOICES } from "constants/"; import { STATE_LIST } from "constants/fetch-keys"; // services import stateService from "lib/services/state.service"; // ui -import { Button, Input } from "ui"; +import { Button, Input, Select, Spinner } from "ui"; // types import type { IState } from "types"; @@ -26,6 +27,12 @@ type Props = { export type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null; +const defaultValues: Partial = { + name: "", + color: "#000000", + group: "backlog", +}; + export const CreateUpdateStateInline: React.FC = ({ workspaceSlug, projectId, @@ -36,17 +43,13 @@ export const CreateUpdateStateInline: React.FC = ({ const { register, handleSubmit, - formState: { errors }, + formState: { errors, isSubmitting }, setError, watch, reset, control, } = useForm({ - defaultValues: { - name: "", - color: "#000000", - group: "backlog", - }, + defaultValues, }); const handleClose = () => { @@ -55,13 +58,13 @@ export const CreateUpdateStateInline: React.FC = ({ }; const onSubmit = async (formData: IState) => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || isSubmitting) return; const payload: IState = { ...formData, }; if (!data) { await stateService - .createState(workspaceSlug, projectId, { ...payload, group: selectedGroup }) + .createState(workspaceSlug, projectId, { ...payload }) .then((res) => { mutate(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false); handleClose(); @@ -77,7 +80,6 @@ export const CreateUpdateStateInline: React.FC = ({ await stateService .updateState(workspaceSlug, projectId, data.id, { ...payload, - group: selectedGroup ?? "backlog", }) .then((res) => { mutate( @@ -108,7 +110,15 @@ export const CreateUpdateStateInline: React.FC = ({ useEffect(() => { if (data === null) return; reset(data); - }, [data]); + }, [data, reset]); + + useEffect(() => { + if (!data) + reset({ + ...defaultValues, + group: selectedGroup ?? "backlog", + }); + }, [selectedGroup, data, reset]); return (
@@ -160,11 +170,26 @@ export const CreateUpdateStateInline: React.FC = ({ register={register} placeholder="Enter state name" validations={{ - required: "Name is required", + required: true, }} error={errors.name} autoComplete="off" /> + {data && ( + = ({ -
); diff --git a/apps/app/components/project/issues/ListView/index.tsx b/apps/app/components/project/issues/ListView/index.tsx index a95c83606..b5dd17336 100644 --- a/apps/app/components/project/issues/ListView/index.tsx +++ b/apps/app/components/project/issues/ListView/index.tsx @@ -93,6 +93,9 @@ const ListView: React.FC = ({

{singleGroup === null || singleGroup === "null" ? selectedGroup === "priority" && "No priority" + : selectedGroup === "created_by" + ? people?.find((p) => p.member.id === singleGroup)?.member + ?.first_name ?? "Loading..." : addSpaceIfCamelCase(singleGroup)}

) : ( diff --git a/apps/app/components/socialbuttons/google-login.tsx b/apps/app/components/socialbuttons/google-login.tsx index 51979cc81..62d9402c7 100644 --- a/apps/app/components/socialbuttons/google-login.tsx +++ b/apps/app/components/socialbuttons/google-login.tsx @@ -1,4 +1,4 @@ -import { FC, CSSProperties } from "react"; +import { FC, CSSProperties, useEffect, useRef, useCallback } from "react"; // next import Script from "next/script"; @@ -10,32 +10,38 @@ export interface IGoogleLoginButton { } export const GoogleLoginButton: FC = (props) => { + const googleSignInButton = useRef(null); + + const loadScript = useCallback(() => { + if (!googleSignInButton.current) return; + window?.google?.accounts.id.initialize({ + client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENTID || "", + callback: props.onSuccess as any, + }); + window?.google?.accounts.id.renderButton( + googleSignInButton.current, + { + type: "standard", + theme: "outline", + size: "large", + logo_alignment: "center", + width: document.getElementById("googleSignInButton")?.offsetWidth, + text: "continue_with", + } as GsiButtonConfiguration // customization attributes + ); + window?.google?.accounts.id.prompt(); // also display the One Tap dialog + }, [props.onSuccess]); + + useEffect(() => { + if (window?.google?.accounts?.id) { + loadScript(); + } + }, [loadScript]); + return ( <> -