From 676355d673c7f24862f54e1482e5b70ab1d0bf6d Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Fri, 16 Dec 2022 20:26:25 +0530 Subject: [PATCH] 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 ( <> -