diff --git a/components/command-palette/index.tsx b/components/command-palette/index.tsx index 0decad5a5..cd3870277 100644 --- a/components/command-palette/index.tsx +++ b/components/command-palette/index.tsx @@ -5,18 +5,18 @@ import { useRouter } from "next/router"; import { Combobox, Dialog, Transition } from "@headlessui/react"; // hooks import useUser from "lib/hooks/useUser"; +import useTheme from "lib/hooks/useTheme"; +import useToast from "lib/hooks/useToast"; // icons import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; import { DocumentPlusIcon, FolderPlusIcon, FolderIcon } from "@heroicons/react/24/outline"; // commons -import { classNames } from "constants/common"; +import { classNames, copyTextToClipboard } from "constants/common"; // components import ShortcutsModal from "components/command-palette/shortcuts"; import CreateProjectModal from "components/project/CreateProjectModal"; import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal"; import CreateUpdateCycleModal from "components/project/cycles/CreateUpdateCyclesModal"; -// hooks -import useTheme from "lib/hooks/useTheme"; // types import { IIssue } from "types"; type ItemType = { @@ -40,6 +40,8 @@ const CommandPalette: React.FC = () => { const { toggleCollapsed } = useTheme(); + const { setToastAlert } = useToast(); + const filteredIssues: IIssue[] = query === "" ? issues?.results ?? [] @@ -72,7 +74,7 @@ const CommandPalette: React.FC = () => { const handleKeyDown = useCallback( (e: KeyboardEvent) => { - if (e.key === "/") { + if (e.ctrlKey && e.key === "/") { e.preventDefault(); setIsPaletteOpen(true); } else if (e.ctrlKey && e.key === "i") { @@ -90,9 +92,28 @@ const CommandPalette: React.FC = () => { } else if (e.ctrlKey && e.key === "q") { e.preventDefault(); setIsCreateCycleModalOpen(true); + } else if (e.ctrlKey && e.altKey && e.key === "c") { + e.preventDefault(); + + if (!router.query.issueId) return; + + const url = new URL(window.location.href); + copyTextToClipboard(url.href) + .then(() => { + setToastAlert({ + type: "success", + title: "Copied to clipboard", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Some error occurred", + }); + }); } }, - [toggleCollapsed] + [toggleCollapsed, setToastAlert, router] ); useEffect(() => { diff --git a/components/command-palette/shortcuts.tsx b/components/command-palette/shortcuts.tsx index e8db47684..f1942d134 100644 --- a/components/command-palette/shortcuts.tsx +++ b/components/command-palette/shortcuts.tsx @@ -59,7 +59,7 @@ const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { { title: "Navigation", shortcuts: [ - { key: "/", description: "To open navigator" }, + { key: "Ctrl + /", description: "To open navigator" }, { key: "↑", description: "Move up" }, { key: "↓", description: "Move down" }, { key: "←", description: "Move left" }, @@ -75,6 +75,10 @@ const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { { key: "Ctrl + i", description: "To open create issue modal" }, { key: "Ctrl + q", description: "To open create cycle modal" }, { key: "Ctrl + h", description: "To open shortcuts guide" }, + { + key: "Ctrl + alt + c", + description: "To copy issue url when on issue detail page.", + }, ], }, ].map(({ title, shortcuts }) => ( diff --git a/components/project/CreateProjectModal.tsx b/components/project/CreateProjectModal.tsx index 5b976847c..2010170c7 100644 --- a/components/project/CreateProjectModal.tsx +++ b/components/project/CreateProjectModal.tsx @@ -92,7 +92,6 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { const checkIdentifier = (slug: string, value: string) => { projectServices.checkProjectIdentifierAvailability(slug, value).then((response) => { console.log(response); - if (response.exists) setError("identifier", { message: "Identifier already exists" }); }); }; diff --git a/components/project/cycles/CycleView.tsx b/components/project/cycles/CycleView.tsx index 5fc0bde36..7cc28ed54 100644 --- a/components/project/cycles/CycleView.tsx +++ b/components/project/cycles/CycleView.tsx @@ -130,11 +130,11 @@ const SprintView: React.FC = ({ - {issue.issue_details.state_detail.name} + {issue.issue_details.state_detail?.name}
diff --git a/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx b/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx index 851ab9ee1..9f3b3951b 100644 --- a/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx +++ b/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx @@ -22,9 +22,9 @@ import { ChevronDownIcon } from "@heroicons/react/24/outline"; import type { IState } from "types"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; projectId: string; data?: IState; + handleClose: () => void; }; const defaultValues: Partial = { @@ -33,14 +33,9 @@ const defaultValues: Partial = { color: "#000000", }; -const CreateUpdateStateModal: React.FC = ({ - isOpen, - setIsOpen, - data, - projectId, -}) => { - const handleClose = () => { - setIsOpen(false); +const CreateUpdateStateModal: React.FC = ({ isOpen, data, projectId, handleClose }) => { + const onClose = () => { + handleClose(); const timeout = setTimeout(() => { reset(defaultValues); clearTimeout(timeout); @@ -70,12 +65,8 @@ const CreateUpdateStateModal: React.FC = ({ await stateService .createState(activeWorkspace.slug, projectId, payload) .then((res) => { - mutate( - STATE_LIST(projectId), - (prevData) => [...(prevData ?? []), res], - false - ); - handleClose(); + mutate(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false); + onClose(); }) .catch((err) => { Object.keys(err).map((key) => { @@ -101,7 +92,7 @@ const CreateUpdateStateModal: React.FC = ({ }, false ); - handleClose(); + onClose(); }) .catch((err) => { Object.keys(err).map((key) => { @@ -115,16 +106,15 @@ const CreateUpdateStateModal: React.FC = ({ useEffect(() => { if (data) { - setIsOpen(true); reset(data); } else { reset(defaultValues); } - }, [data, setIsOpen, reset]); + }, [data, reset]); return ( - + = ({
- + {data ? "Update" : "Create"} State
@@ -188,8 +175,7 @@ const CreateUpdateStateModal: React.FC = ({ )} @@ -214,14 +200,10 @@ const CreateUpdateStateModal: React.FC = ({ ( + render={({ field: { value, onChange } }) => ( - onChange(value.hex) - } + onChange={(value) => onChange(value.hex)} /> )} /> @@ -245,7 +227,7 @@ const CreateUpdateStateModal: React.FC = ({
- - - -
- - )} - + { + return { value: state.id, display: state.name }; + })} + value={value} + optionsFontsize="sm" + onChange={onChange} + icon={} + footerOption={ + + } + /> )} > diff --git a/components/project/issues/CreateUpdateIssueModal/index.tsx b/components/project/issues/CreateUpdateIssueModal/index.tsx index d27486795..d6a717478 100644 --- a/components/project/issues/CreateUpdateIssueModal/index.tsx +++ b/components/project/issues/CreateUpdateIssueModal/index.tsx @@ -1,10 +1,18 @@ import React, { useEffect, useState } from "react"; +// next +import Link from "next/link"; +import { useRouter } from "next/router"; // swr import { mutate } from "swr"; // react hook form import { useForm } from "react-hook-form"; // fetching keys -import { PROJECT_ISSUES_DETAILS, PROJECT_ISSUES_LIST, CYCLE_ISSUES } from "constants/fetch-keys"; +import { + PROJECT_ISSUES_DETAILS, + PROJECT_ISSUES_LIST, + CYCLE_ISSUES, + USER_ISSUE, +} from "constants/fetch-keys"; // headless import { Dialog, Transition } from "@headlessui/react"; // services @@ -15,7 +23,7 @@ import useToast from "lib/hooks/useToast"; // ui import { Button, Input, TextArea } from "ui"; // commons -import { renderDateFormat } from "constants/common"; +import { renderDateFormat, cosineSimilarity } from "constants/common"; // components import SelectState from "./SelectState"; import SelectCycles from "./SelectCycles"; @@ -55,6 +63,10 @@ const CreateUpdateIssuesModal: React.FC = ({ const [isCycleModalOpen, setIsCycleModalOpen] = useState(false); const [isStateModalOpen, setIsStateModalOpen] = useState(false); + const [mostSimilarIssue, setMostSimilarIssue] = useState(); + + const router = useRouter(); + const handleClose = () => { setIsOpen(false); if (data) { @@ -69,7 +81,7 @@ const CreateUpdateIssuesModal: React.FC = ({ }, 500); }; - const { activeWorkspace, activeProject } = useUser(); + const { activeWorkspace, activeProject, user, issues } = useUser(); const { setToastAlert } = useToast(); @@ -165,6 +177,7 @@ const CreateUpdateIssuesModal: React.FC = ({ }, false ); + if (formData.sprints && formData.sprints !== null) { await addIssueToSprint(res.id, formData.sprints, formData); } @@ -175,6 +188,15 @@ const CreateUpdateIssuesModal: React.FC = ({ type: "success", message: `Issue ${data ? "updated" : "created"} successfully`, }); + if (formData.assignees_list.some((assignee) => assignee === user?.id)) { + mutate( + USER_ISSUE, + (prevData) => { + return [res, ...(prevData ?? [])]; + }, + false + ); + } }) .catch((err) => { Object.keys(err).map((key) => { @@ -235,13 +257,17 @@ const CreateUpdateIssuesModal: React.FC = ({ }); }, [data, prePopulateData, reset, projectId, activeProject, isOpen, watch]); + useEffect(() => { + return () => setMostSimilarIssue(undefined); + }, []); + return ( <> {activeProject && ( <> setIsStateModalOpen(false)} projectId={activeProject?.id} /> = ({ label="Name" name="name" rows={1} + onChange={(e) => { + const value = e.target.value; + const similarIssue = issues?.results.find( + (i) => cosineSimilarity(i.name, value) > 0.7 + ); + setMostSimilarIssue(similarIssue?.id); + }} className="resize-none" placeholder="Enter name" autoComplete="off" @@ -302,6 +335,42 @@ const CreateUpdateIssuesModal: React.FC = ({ required: "Name is required", }} /> + {mostSimilarIssue && ( +
+

+ Did you mean{" "} + + ? +

+ +
+ )}