diff --git a/apps/app/components/command-palette/index.tsx b/apps/app/components/command-palette/index.tsx index b626eac7a..16053564f 100644 --- a/apps/app/components/command-palette/index.tsx +++ b/apps/app/components/command-palette/index.tsx @@ -1,6 +1,10 @@ import React, { useState, useCallback, useEffect } from "react"; // next import { useRouter } from "next/router"; +// swr +import { mutate } from "swr"; +// react hook form +import { SubmitHandler, useForm } from "react-hook-form"; // headless ui import { Combobox, Dialog, Transition } from "@headlessui/react"; // hooks @@ -22,9 +26,11 @@ import CreateProjectModal from "components/project/CreateProjectModal"; import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal"; import CreateUpdateCycleModal from "components/project/cycles/CreateUpdateCyclesModal"; // types -import { IIssue } from "types"; +import { IIssue, IProject, IssueResponse } from "types"; import { Button } from "ui"; -import { SubmitHandler, useForm } from "react-hook-form"; +import issuesServices from "lib/services/issues.services"; +// fetch keys +import { PROJECTS_LIST, PROJECT_ISSUES_LIST } from "constants/fetch-keys"; type ItemType = { name: string; @@ -33,7 +39,7 @@ type ItemType = { }; type FormInput = { - issue: string[]; + issue_ids: string[]; }; const CommandPalette: React.FC = () => { @@ -47,7 +53,7 @@ const CommandPalette: React.FC = () => { const [isShortcutsModalOpen, setIsShortcutsModalOpen] = useState(false); const [isCreateCycleModalOpen, setIsCreateCycleModalOpen] = useState(false); - const { issues, activeProject } = useUser(); + const { activeWorkspace, activeProject, issues, cycles } = useUser(); const { toggleCollapsed } = useTheme(); @@ -59,6 +65,14 @@ const CommandPalette: React.FC = () => { : issues?.results.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ?? []; + const { + register, + formState: { errors, isSubmitting }, + handleSubmit, + reset, + setError, + } = useForm(); + const quickActions = [ { name: "Add new issue...", @@ -81,6 +95,7 @@ const CommandPalette: React.FC = () => { const handleCommandPaletteClose = () => { setIsPaletteOpen(false); setQuery(""); + reset(); }; const handleKeyDown = useCallback( @@ -127,21 +142,42 @@ const CommandPalette: React.FC = () => { [toggleCollapsed, setToastAlert, router] ); - const { - register, - formState: { errors, isSubmitting }, - handleSubmit, - reset, - setError, - control, - } = useForm(); - const handleDelete: SubmitHandler = (data) => { - console.log("Deleting... " + JSON.stringify(data)); + if (activeWorkspace && activeProject && data.issue_ids) { + issuesServices + .bulkDeleteIssues(activeWorkspace.slug, activeProject.id, data) + .then((res) => { + mutate( + PROJECT_ISSUES_LIST(activeWorkspace.slug, activeProject.id), + (prevData) => { + return { + ...(prevData as IssueResponse), + count: (prevData?.results ?? []).filter( + (p) => !data.issue_ids.some((id) => p.id === id) + ).length, + results: (prevData?.results ?? []).filter( + (p) => !data.issue_ids.some((id) => p.id === id) + ), + }; + }, + false + ); + }) + .catch((e) => { + console.log(e); + }); + } }; const handleAddToCycle: SubmitHandler = (data) => { - console.log("Adding to cycle..."); + if (activeWorkspace && activeProject && data.issue_ids) { + issuesServices + .bulkAddIssuesToCycle(activeWorkspace.slug, activeProject.id, "", data) + .then((res) => {}) + .catch((e) => { + console.log(e); + }); + } }; useEffect(() => { @@ -254,10 +290,16 @@ const CommandPalette: React.FC = () => { /> */} - {issue.name} + {active && ( Jump to... diff --git a/components/lexical/config.ts b/apps/app/components/lexical/config.ts similarity index 100% rename from components/lexical/config.ts rename to apps/app/components/lexical/config.ts diff --git a/components/lexical/editor.tsx b/apps/app/components/lexical/editor.tsx similarity index 95% rename from components/lexical/editor.tsx rename to apps/app/components/lexical/editor.tsx index e4e9ef359..3685de22e 100644 --- a/components/lexical/editor.tsx +++ b/apps/app/components/lexical/editor.tsx @@ -19,6 +19,7 @@ import { LexicalToolbar } from "./toolbar"; import { initialConfig } from "./config"; // helpers import { getValidatedValue } from "./helpers/editor"; +import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; export interface RichTextEditorProps { onChange: (state: string) => void; @@ -51,6 +52,7 @@ const RichTextEditor: FC = (props) => { contentEditable={ } + ErrorBoundary={LexicalErrorBoundary} placeholder={
Enter some text... diff --git a/components/lexical/helpers/editor.ts b/apps/app/components/lexical/helpers/editor.ts similarity index 100% rename from components/lexical/helpers/editor.ts rename to apps/app/components/lexical/helpers/editor.ts diff --git a/components/lexical/helpers/node.ts b/apps/app/components/lexical/helpers/node.ts similarity index 100% rename from components/lexical/helpers/node.ts rename to apps/app/components/lexical/helpers/node.ts diff --git a/components/lexical/plugins/code-highlight.tsx b/apps/app/components/lexical/plugins/code-highlight.tsx similarity index 100% rename from components/lexical/plugins/code-highlight.tsx rename to apps/app/components/lexical/plugins/code-highlight.tsx diff --git a/components/lexical/plugins/read-only.tsx b/apps/app/components/lexical/plugins/read-only.tsx similarity index 100% rename from components/lexical/plugins/read-only.tsx rename to apps/app/components/lexical/plugins/read-only.tsx diff --git a/components/lexical/theme.ts b/apps/app/components/lexical/theme.ts similarity index 100% rename from components/lexical/theme.ts rename to apps/app/components/lexical/theme.ts diff --git a/components/lexical/toolbar/block-type-select.tsx b/apps/app/components/lexical/toolbar/block-type-select.tsx similarity index 100% rename from components/lexical/toolbar/block-type-select.tsx rename to apps/app/components/lexical/toolbar/block-type-select.tsx diff --git a/components/lexical/toolbar/floating-link-editor.tsx b/apps/app/components/lexical/toolbar/floating-link-editor.tsx similarity index 100% rename from components/lexical/toolbar/floating-link-editor.tsx rename to apps/app/components/lexical/toolbar/floating-link-editor.tsx diff --git a/components/lexical/toolbar/index.tsx b/apps/app/components/lexical/toolbar/index.tsx similarity index 100% rename from components/lexical/toolbar/index.tsx rename to apps/app/components/lexical/toolbar/index.tsx diff --git a/components/lexical/viewer.tsx b/apps/app/components/lexical/viewer.tsx similarity index 94% rename from components/lexical/viewer.tsx rename to apps/app/components/lexical/viewer.tsx index bad4f41c7..83be573c0 100644 --- a/components/lexical/viewer.tsx +++ b/apps/app/components/lexical/viewer.tsx @@ -14,6 +14,7 @@ import ReadOnlyPlugin from "./plugins/read-only"; import { initialConfig } from "./config"; // helpers import { getValidatedValue } from "./helpers/editor"; +import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; export interface RichTextViewerProps { id: string; @@ -38,6 +39,7 @@ const RichTextViewer: FC = (props) => { contentEditable={ } + ErrorBoundary={LexicalErrorBoundary} placeholder={
Enter some text... diff --git a/apps/app/components/project/issues/BoardView/SingleBoard.tsx b/apps/app/components/project/issues/BoardView/SingleBoard.tsx index 4f2c3b1d6..928261f64 100644 --- a/apps/app/components/project/issues/BoardView/SingleBoard.tsx +++ b/apps/app/components/project/issues/BoardView/SingleBoard.tsx @@ -55,9 +55,6 @@ const SingleBoard: React.FC = ({ // Collapse/Expand const [show, setState] = useState(true); - // Edit state name - const [showInput, setInput] = useState(false); - if (selectedGroup === "priority") groupTitle === "high" ? (bgColor = "#dc2626") @@ -80,57 +77,52 @@ const SingleBoard: React.FC = ({
- {showInput ? null : ( -
- -
+ +
+ { - // setInput(true); + /> +

- -

- {groupTitle === null || groupTitle === "null" - ? "None" - : createdBy - ? createdBy - : addSpaceIfCamelCase(groupTitle)} -

- - {groupedByIssues[groupTitle].length} - -

+ {groupTitle === null || groupTitle === "null" + ? "None" + : createdBy + ? createdBy + : addSpaceIfCamelCase(groupTitle)} + + + {groupedByIssues[groupTitle].length} +
- )} +
- -
- {sidebarOptions.map((item) => ( -
-
- -

{item.label}

-
-
- ( - { - if (item.name === "cycle") handleCycleChange(value); - else submitChanges({ [item.name]: value }); - }} - className="flex-shrink-0" - > - {({ open }) => ( -
- - - {value - ? Array.isArray(value) - ? value - .map( - (i: any) => - item.options?.find((option) => option.value === i)?.label - ) - .join(", ") || item.label - : item.options?.find((option) => option.value === value)?.label - : "None"} - - - - - - -
- {item.options ? ( - item.options.length > 0 ? ( - item.options.map((option) => ( - - `${ - active || selected - ? "text-white bg-theme" - : "text-gray-900" - } ${ - item.label === "Priority" && "capitalize" - } cursor-pointer select-none relative p-2 rounded-md truncate` - } - value={option.value} - > - {option.label} - - )) - ) : ( -
No {item.label}s found
- ) - ) : ( - - )} -
-
-
-
+
+
+

+ {activeProject?.identifier}-{issueDetail?.sequence_id} +

+
+ + +
+
+
+ {sidebarSections.map((section, index) => ( +
+ {section.map((item) => ( +
+
+ +

{item.label}

+
+
+ {item.name === "target_date" ? ( + ( + { + submitChanges({ target_date: e.target.value }); + onChange(e.target.value); + }} + className="hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300" + /> )} - - )} - /> -
-
- ))} -
-
- - -
-
-
-
- -

Label

-
-
- ( - submitChanges({ labels_list: value })} - className="flex-shrink-0" - > - {({ open }) => ( - <> - Label -
- - - {value && value.length > 0 - ? value - .map( - (i: string) => - issueLabels?.find((option) => option.id === i)?.name - ) - .join(", ") - : "None"} - - - + /> + ) : ( + ( + { + if (item.name === "cycle") handleCycleChange(value); + else submitChanges({ [item.name]: value }); + }} + className="flex-shrink-0" + > + {({ open }) => ( +
+ + + {value + ? Array.isArray(value) + ? value + .map( + (i: any) => + item.options?.find((option) => option.value === i) + ?.label + ) + .join(", ") || item.label + : item.options?.find((option) => option.value === value) + ?.label + : "None"} + + + - - -
- {issueLabels ? ( - issueLabels.length > 0 ? ( - issueLabels.map((label: any) => ( - - `${ - active || selected - ? "text-white bg-theme" - : "text-gray-900" - } cursor-pointer select-none relative p-2 rounded-md truncate` - } - value={label.id} - > - {label.name} - - )) - ) : ( -
No labels found
- ) - ) : ( - - )} -
-
-
-
- + + +
+ {item.options ? ( + item.options.length > 0 ? ( + item.options.map((option) => ( + + `${ + active || selected + ? "text-white bg-theme" + : "text-gray-900" + } ${ + item.label === "Priority" && "capitalize" + } cursor-pointer select-none relative p-2 rounded-md truncate` + } + value={option.value} + > + {option.label} + + )) + ) : ( +
No {item.label}s found
+ ) + ) : ( + + )} +
+
+
+
+ )} +
+ )} + /> + )} +
+
+ ))} +
+ ))} +
+
+
Add new label
+
+
+ + {({ open }) => ( + <> + + {watch("colour") && watch("colour") !== "" && ( + )} - - )} - /> -
+ + + + + + ( + onChange(value.hex)} /> + )} + /> + + + + )} + +
+ + + +
+
+ +

Label

+
+
+ ( + submitChanges({ labels_list: value })} + className="flex-shrink-0" + > + {({ open }) => ( + <> + Label +
+ + + {value && value.length > 0 + ? value + .map( + (i: string) => + issueLabels?.find((option) => option.id === i)?.name + ) + .join(", ") + : "None"} + + + + + + +
+ {issueLabels ? ( + issueLabels.length > 0 ? ( + issueLabels.map((label: IIssueLabels) => ( + + `${ + active || selected + ? "text-white bg-theme" + : "text-gray-900" + } flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate` + } + value={label.id} + > + + {label.name} + + )) + ) : ( +
No labels found
+ ) + ) : ( + + )} +
+
+
+
+ + )} +
+ )} + />
diff --git a/apps/app/components/project/issues/issue-detail/activity/index.tsx b/apps/app/components/project/issues/issue-detail/activity/index.tsx index 45965ca24..ab80eb9c3 100644 --- a/apps/app/components/project/issues/issue-detail/activity/index.tsx +++ b/apps/app/components/project/issues/issue-detail/activity/index.tsx @@ -1,6 +1,7 @@ // next import Image from "next/image"; import { + CalendarDaysIcon, ChartBarIcon, ChatBubbleBottomCenterTextIcon, Squares2X2Icon, @@ -19,6 +20,7 @@ const activityIcons = { priority: , name: , description: , + target_date: , }; const IssueActivitySection: React.FC = ({ issueActivities, states }) => { @@ -64,43 +66,41 @@ const IssueActivitySection: React.FC = ({ issueActivities, states }) => {
)} -
+

- {activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "} + + {activity.actor_detail.first_name} {activity.actor_detail.last_name} + {" "} {activity.verb}{" "} {activity.verb !== "created" ? ( {activity.field ?? "commented"} ) : ( " this issue" )} + {timeAgo(activity.created_at)}

-

{timeAgo(activity.created_at)}

{activity.verb !== "created" && ( -
+
- From:{" "} - - {activity.field === "state" - ? activity.old_value - ? addSpaceIfCamelCase( - states?.find((s) => s.id === activity.old_value)?.name ?? "" - ) - : "None" - : activity.old_value} - + From: + {activity.field === "state" + ? activity.old_value + ? addSpaceIfCamelCase( + states?.find((s) => s.id === activity.old_value)?.name ?? "" + ) + : "None" + : activity.old_value}
- To:{" "} - - {activity.field === "state" - ? activity.new_value - ? addSpaceIfCamelCase( - states?.find((s) => s.id === activity.new_value)?.name ?? "" - ) - : "None" - : activity.new_value} - + To: + {activity.field === "state" + ? activity.new_value + ? addSpaceIfCamelCase( + states?.find((s) => s.id === activity.new_value)?.name ?? "" + ) + : "None" + : activity.new_value}
)} diff --git a/apps/app/constants/api-routes.ts b/apps/app/constants/api-routes.ts index ea22163f7..136e13d3b 100644 --- a/apps/app/constants/api-routes.ts +++ b/apps/app/constants/api-routes.ts @@ -103,6 +103,13 @@ export const ISSUE_LABELS = (workspaceSlug: string, projectId: string) => export const FILTER_STATE_ISSUES = (workspaceSlug: string, projectId: string, state: string) => `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/?state=${state}`; +export const BULK_DELETE_ISSUES = (workspaceSlug: string, projectId: string) => + `/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-delete-issues/`; +export const BULK_ADD_ISSUES_TO_CYCLE = ( + workspaceSlug: string, + projectId: string, + cycleId: string +) => `/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/bulk-assign-issues/`; // states export const STATES_ENDPOINT = (workspaceSlug: string, projectId: string) => diff --git a/apps/app/lib/services/api.service.ts b/apps/app/lib/services/api.service.ts index 9a5d98bc3..112a87f9e 100644 --- a/apps/app/lib/services/api.service.ts +++ b/apps/app/lib/services/api.service.ts @@ -78,10 +78,11 @@ abstract class APIService { }); } - delete(url: string, config = {}): Promise { + delete(url: string, data?: any, config = {}): Promise { return axios({ method: "delete", url: this.baseURL + url, + data: data, headers: this.getAccessToken() ? this.getHeaders() : {}, ...config, }); diff --git a/apps/app/lib/services/issues.services.ts b/apps/app/lib/services/issues.services.ts index 7024072fc..0e285602e 100644 --- a/apps/app/lib/services/issues.services.ts +++ b/apps/app/lib/services/issues.services.ts @@ -8,6 +8,8 @@ import { ISSUE_PROPERTIES_ENDPOINT, CYCLE_DETAIL, ISSUE_LABELS, + BULK_DELETE_ISSUES, + BULK_ADD_ISSUES_TO_CYCLE, } from "constants/api-routes"; // services import APIService from "lib/services/api.service"; @@ -235,6 +237,31 @@ class ProjectIssuesServices extends APIService { throw error?.response?.data; }); } + + async bulkDeleteIssues(workspace_slug: string, projectId: string, data: any): Promise { + return this.delete(BULK_DELETE_ISSUES(workspace_slug, projectId), data) + .then((response) => { + return response?.data; + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async bulkAddIssuesToCycle( + workspace_slug: string, + projectId: string, + cycleId: string, + data: any + ): Promise { + return this.post(BULK_ADD_ISSUES_TO_CYCLE(workspace_slug, projectId, cycleId), data) + .then((response) => { + return response?.data; + }) + .catch((error) => { + throw error?.response?.data; + }); + } } export default new ProjectIssuesServices(); diff --git a/apps/app/package.json b/apps/app/package.json index fc3e645d6..a596ca4b9 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -11,6 +11,9 @@ "dependencies": { "@headlessui/react": "^1.7.3", "@heroicons/react": "^2.0.12", + "@lexical/list": "^0.6.4", + "@lexical/react": "^0.6.4", + "@lexical/utils": "^0.6.4", "axios": "^1.1.3", "js-cookie": "^3.0.1", "lexical": "^0.6.4", diff --git a/apps/app/pages/invitations.tsx b/apps/app/pages/invitations.tsx index 5c9f334d5..25bb7d324 100644 --- a/apps/app/pages/invitations.tsx +++ b/apps/app/pages/invitations.tsx @@ -21,11 +21,7 @@ import { Button, Spinner, EmptySpace, EmptySpaceItem } from "ui"; import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline"; // types import type { IWorkspaceInvitation } from "types"; -<<<<<<< Updated upstream -import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline"; -import { EmptySpace, EmptySpaceItem } from "ui/EmptySpace"; -======= ->>>>>>> Stashed changes +import Link from "next/link"; const OnBoard: NextPage = () => { const router = useRouter(); @@ -71,16 +67,9 @@ const OnBoard: NextPage = () => { }); }; - useEffect(() => { -<<<<<<< Updated upstream - userService.updateUserOnBoard().then((response) => { - console.log(response); - }); - }, []); -======= - if (workspaces && workspaces.length === 0) setCanRedirect(false); - }, [workspaces]); ->>>>>>> Stashed changes + // useEffect(() => { + // if (workspaces && workspaces.length === 0) setCanRedirect(false); + // }, [workspaces]); return ( {
{invitations && workspaces ? ( invitations.length > 0 ? ( -<<<<<<< Updated upstream

Join your workspaces

@@ -141,11 +129,6 @@ const OnBoard: NextPage = () => {
))}
-======= -
-
- ->>>>>>> Stashed changes

Workspace Invitations

diff --git a/apps/app/pages/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/projects/[projectId]/issues/[issueId].tsx index 4d98b33c5..740529415 100644 --- a/apps/app/pages/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/projects/[projectId]/issues/[issueId].tsx @@ -53,7 +53,19 @@ const IssueDetail: NextPage = () => { handleSubmit, reset, control, - } = useForm({}); + } = useForm({ + defaultValues: { + name: "", + description: "", + state: "", + assignees_list: [], + priority: "low", + blockers_list: [], + blocked_list: [], + target_date: new Date().toString(), + cycle: "", + }, + }); const { data: issueActivities } = useSWR( activeWorkspace && projectId && issueId ? PROJECT_ISSUES_ACTIVITY : null, @@ -150,21 +162,18 @@ const IssueDetail: NextPage = () => { />

- - - -
-

{`${activeProject?.name ?? "Project"}/${ - activeProject?.identifier ?? "..." - }-${issueDetail?.sequence_id ?? "..."}`}

+ + + +