From 4424d67073871ebc02f6471c5d79800d37d0be80 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 21 Jul 2023 12:37:48 +0530 Subject: [PATCH 01/99] style: ui improvements (#1605) * style: automation setting border * style: sidebar ui improvement --- .../automation/auto-archive-automation.tsx | 2 +- .../automation/auto-close-automation.tsx | 2 +- .../notifications/notification-popover.tsx | 35 +++++++++++-------- .../app/components/workspace/help-section.tsx | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/apps/app/components/automation/auto-archive-automation.tsx b/apps/app/components/automation/auto-archive-automation.tsx index 8772371c4..07ac86460 100644 --- a/apps/app/components/automation/auto-archive-automation.tsx +++ b/apps/app/components/automation/auto-archive-automation.tsx @@ -28,7 +28,7 @@ export const AutoArchiveAutomation: React.FC = ({ projectDetails, handleC handleClose={() => setmonthModal(false)} handleChange={handleChange} /> -
+

Auto-archive closed issues

diff --git a/apps/app/components/automation/auto-close-automation.tsx b/apps/app/components/automation/auto-close-automation.tsx index 03df06235..3023328e7 100644 --- a/apps/app/components/automation/auto-close-automation.tsx +++ b/apps/app/components/automation/auto-close-automation.tsx @@ -77,7 +77,7 @@ export const AutoCloseAutomation: React.FC = ({ projectDetails, handleCha handleChange={handleChange} /> -
+

Auto-close inactive issues

diff --git a/apps/app/components/notifications/notification-popover.tsx b/apps/app/components/notifications/notification-popover.tsx index 255d8af04..860dc4f60 100644 --- a/apps/app/components/notifications/notification-popover.tsx +++ b/apps/app/components/notifications/notification-popover.tsx @@ -94,21 +94,28 @@ export const NotificationPopover = () => { {({ open: isActive, close: closePopover }) => ( <> - - - {sidebarCollapse ? null : Notifications} - {totalNotificationCount && totalNotificationCount > 0 ? ( - - {getNumberCount(totalNotificationCount)} - - ) : null} - + + + {sidebarCollapse ? null : Notifications} + {totalNotificationCount && totalNotificationCount > 0 ? ( + + {getNumberCount(totalNotificationCount)} + + ) : null} + + = ({ setS }`} > {!sidebarCollapse && ( -
+
Free Plan
)} From 0e5c0fe31ec22ab002425d543f7f65d59e43cfa6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 21 Jul 2023 14:29:06 +0530 Subject: [PATCH 02/99] refactor: issue search (#1607) --- .../modals/existing-issues-list-modal.tsx | 46 ++++++----------- .../issues/parent-issues-list-modal.tsx | 49 ++++++------------- 2 files changed, 32 insertions(+), 63 deletions(-) diff --git a/apps/app/components/core/modals/existing-issues-list-modal.tsx b/apps/app/components/core/modals/existing-issues-list-modal.tsx index 39237ce9a..71bcef561 100644 --- a/apps/app/components/core/modals/existing-issues-list-modal.tsx +++ b/apps/app/components/core/modals/existing-issues-list-modal.tsx @@ -42,7 +42,6 @@ export const ExistingIssuesListModal: React.FC = ({ }) => { const [searchTerm, setSearchTerm] = useState(""); const [issues, setIssues] = useState([]); - const [isLoading, setIsLoading] = useState(false); const [isSearching, setIsSearching] = useState(false); const [selectedIssues, setSelectedIssues] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); @@ -97,31 +96,18 @@ export const ExistingIssuesListModal: React.FC = ({ }; useEffect(() => { - if (!workspaceSlug || !projectId) return; + if (!isOpen || !workspaceSlug || !projectId) return; - setIsLoading(true); + setIsSearching(true); - if (debouncedSearchTerm) { - setIsSearching(true); - - projectService - .projectIssuesSearch(workspaceSlug as string, projectId as string, { - search: debouncedSearchTerm, - ...searchParams, - }) - .then((res) => { - setIssues(res); - }) - .finally(() => { - setIsLoading(false); - setIsSearching(false); - }); - } else { - setIssues([]); - setIsLoading(false); - setIsSearching(false); - } - }, [debouncedSearchTerm, workspaceSlug, projectId, searchParams]); + projectService + .projectIssuesSearch(workspaceSlug as string, projectId as string, { + search: debouncedSearchTerm, + ...searchParams, + }) + .then((res) => setIssues(res)) + .finally(() => setIsSearching(false)); + }, [debouncedSearchTerm, isOpen, projectId, searchParams, workspaceSlug]); return ( <> @@ -169,7 +155,7 @@ export const ExistingIssuesListModal: React.FC = ({ aria-hidden="true" /> setSearchTerm(e.target.value)} @@ -206,20 +192,20 @@ export const ExistingIssuesListModal: React.FC = ({ )}
- - {debouncedSearchTerm !== "" && ( + + {searchTerm !== "" && (
Search results for{" "} {'"'} - {debouncedSearchTerm} + {searchTerm} {'"'} {" "} in project:
)} - {!isLoading && + {!isSearching && issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && ( @@ -235,7 +221,7 @@ export const ExistingIssuesListModal: React.FC = ({
)} - {isLoading || isSearching ? ( + {isSearching ? ( diff --git a/apps/app/components/issues/parent-issues-list-modal.tsx b/apps/app/components/issues/parent-issues-list-modal.tsx index d591bb896..65c67616e 100644 --- a/apps/app/components/issues/parent-issues-list-modal.tsx +++ b/apps/app/components/issues/parent-issues-list-modal.tsx @@ -24,7 +24,6 @@ type Props = { onChange: (issue: ISearchIssueResponse) => void; projectId: string; issueId?: string; - customDisplay?: JSX.Element; }; export const ParentIssuesListModal: React.FC = ({ @@ -34,11 +33,9 @@ export const ParentIssuesListModal: React.FC = ({ onChange, projectId, issueId, - customDisplay, }) => { const [searchTerm, setSearchTerm] = useState(""); const [issues, setIssues] = useState([]); - const [isLoading, setIsLoading] = useState(false); const [isSearching, setIsSearching] = useState(false); const debouncedSearchTerm: string = useDebounce(searchTerm, 500); @@ -52,32 +49,19 @@ export const ParentIssuesListModal: React.FC = ({ }; useEffect(() => { - if (!workspaceSlug || !projectId) return; + if (!isOpen || !workspaceSlug || !projectId) return; - setIsLoading(true); + setIsSearching(true); - if (debouncedSearchTerm) { - setIsSearching(true); - - projectService - .projectIssuesSearch(workspaceSlug as string, projectId as string, { - search: debouncedSearchTerm, - parent: true, - issue_id: issueId, - }) - .then((res) => { - setIssues(res); - }) - .finally(() => { - setIsLoading(false); - setIsSearching(false); - }); - } else { - setIssues([]); - setIsLoading(false); - setIsSearching(false); - } - }, [debouncedSearchTerm, workspaceSlug, projectId, issueId]); + projectService + .projectIssuesSearch(workspaceSlug as string, projectId as string, { + search: debouncedSearchTerm, + parent: true, + issue_id: issueId, + }) + .then((res) => setIssues(res)) + .finally(() => setIsSearching(false)); + }, [debouncedSearchTerm, isOpen, issueId, projectId, workspaceSlug]); return ( <> @@ -124,28 +108,27 @@ export const ParentIssuesListModal: React.FC = ({ aria-hidden="true" /> setSearchTerm(e.target.value)} displayValue={() => ""} />
- {customDisplay &&
{customDisplay}
} - {debouncedSearchTerm !== "" && ( + {searchTerm !== "" && (
Search results for{" "} {'"'} - {debouncedSearchTerm} + {searchTerm} {'"'} {" "} in project:
)} - {!isLoading && + {!isSearching && issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && ( @@ -161,7 +144,7 @@ export const ParentIssuesListModal: React.FC = ({
)} - {isLoading || isSearching ? ( + {isSearching ? ( From 6c2600efa7a75809ab5c3c7181280c46fe7d12ca Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sat, 22 Jul 2023 14:53:48 +0530 Subject: [PATCH 03/99] feat: cross-project issue linking (#1612) * feat: cross project issue linking * fix: remove parent issue mutation * fix: build error --- .../modals/existing-issues-list-modal.tsx | 68 +++++-- apps/app/components/issues/form.tsx | 4 +- apps/app/components/issues/main-content.tsx | 57 +++--- .../issues/parent-issues-list-modal.tsx | 51 ++++- .../issues/sidebar-select/blocked.tsx | 29 +-- .../issues/sidebar-select/blocker.tsx | 29 +-- .../issues/sidebar-select/parent.tsx | 6 +- .../app/components/issues/sub-issues-list.tsx | 186 +++++------------- .../components/ui/dropdowns/custom-menu.tsx | 4 +- apps/app/components/ui/toggle-switch.tsx | 2 +- apps/app/components/ui/tooltip.tsx | 2 +- .../app/components/workspace/help-section.tsx | 8 +- apps/app/types/issues.d.ts | 13 +- apps/app/types/projects.d.ts | 5 +- apps/app/types/state.d.ts | 11 +- 15 files changed, 244 insertions(+), 231 deletions(-) diff --git a/apps/app/components/core/modals/existing-issues-list-modal.tsx b/apps/app/components/core/modals/existing-issues-list-modal.tsx index 71bcef561..f4231fee0 100644 --- a/apps/app/components/core/modals/existing-issues-list-modal.tsx +++ b/apps/app/components/core/modals/existing-issues-list-modal.tsx @@ -13,8 +13,9 @@ import useToast from "hooks/use-toast"; import useIssuesView from "hooks/use-issues-view"; import useDebounce from "hooks/use-debounce"; // ui -import { Loader, PrimaryButton, SecondaryButton } from "components/ui"; +import { Loader, PrimaryButton, SecondaryButton, ToggleSwitch, Tooltip } from "components/ui"; // icons +import { LaunchOutlined } from "@mui/icons-material"; import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { LayerDiagonalIcon } from "components/icons"; // types @@ -45,6 +46,7 @@ export const ExistingIssuesListModal: React.FC = ({ const [isSearching, setIsSearching] = useState(false); const [selectedIssues, setSelectedIssues] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); + const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); const debouncedSearchTerm: string = useDebounce(searchTerm, 500); @@ -59,6 +61,7 @@ export const ExistingIssuesListModal: React.FC = ({ onClose(); setSearchTerm(""); setSelectedIssues([]); + setIsWorkspaceLevel(false); }; const onSubmit = async () => { @@ -104,10 +107,11 @@ export const ExistingIssuesListModal: React.FC = ({ .projectIssuesSearch(workspaceSlug as string, projectId as string, { search: debouncedSearchTerm, ...searchParams, + workspace_search: isWorkspaceLevel, }) .then((res) => setIssues(res)) .finally(() => setIsSearching(false)); - }, [debouncedSearchTerm, isOpen, projectId, searchParams, workspaceSlug]); + }, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]); return ( <> @@ -162,7 +166,7 @@ export const ExistingIssuesListModal: React.FC = ({ />
-
+
{selectedIssues.length > 0 ? (
{selectedIssues.map((issue) => ( @@ -190,6 +194,25 @@ export const ExistingIssuesListModal: React.FC = ({ No issues selected
)} + +
+ setIsWorkspaceLevel((prevData) => !prevData)} + /> + +
+
@@ -242,22 +265,37 @@ export const ExistingIssuesListModal: React.FC = ({ htmlFor={`issue-${issue.id}`} value={issue} className={({ active }) => - `flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${ + `group flex items-center justify-between gap-2 w-full cursor-pointer select-none rounded-md px-3 py-2 text-custom-text-200 ${ active ? "bg-custom-background-80 text-custom-text-100" : "" } ${selected ? "text-custom-text-100" : ""}` } > - - - - {issue.project__identifier}-{issue.sequence_id} - - {issue.name} +
+ + + + {issue.project__identifier}-{issue.sequence_id} + + {issue.name} +
+ e.stopPropagation()} + > + + ); })} diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 02efa8f88..2a17a07a8 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -75,6 +75,7 @@ const defaultValues: Partial = { assignees_list: [], labels: [], labels_list: [], + target_date: null, }; export interface IssueFormProps { @@ -271,7 +272,6 @@ export const IssueForm: FC = ({
{watch("parent") && - watch("parent") !== "" && (fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && selectedParentIssue && (
@@ -476,7 +476,7 @@ export const IssueForm: FC = ({ )} {(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && ( - {watch("parent") && watch("parent") !== "" ? ( + {watch("parent") ? ( <> = ({ ) : null ); + const siblingIssuesList = siblingIssues?.sub_issues.filter((i) => i.id !== issueDetails.id); return ( <>
- {issueDetails?.parent && issueDetails.parent !== "" ? ( + {issueDetails?.parent ? (
- + - {issueDetails.project_detail.identifier}-{issueDetails.parent_detail?.sequence_id} + {issueDetails.parent_detail?.project_detail.identifier}- + {issueDetails.parent_detail?.sequence_id} {issueDetails.parent_detail?.name.substring(0, 50)} @@ -77,29 +81,28 @@ export const IssueMainContent: React.FC = ({ - {siblingIssues && siblingIssues.sub_issues.length > 0 ? ( - <> -

Sibling issues

- {siblingIssues.sub_issues.map((issue) => { - if (issue.id !== issueDetails.id) - return ( - - {issueDetails.project_detail.identifier}-{issue.sequence_id} - - ); - })} - - ) : ( -

- No sibling issues -

- )} + {siblingIssuesList ? ( + siblingIssuesList.length > 0 ? ( + <> +

Sibling issues

+ {siblingIssuesList.map((issue) => ( + + {issueDetails.project_detail.identifier}-{issue.sequence_id} + + ))} + + ) : ( +

+ No sibling issues +

+ ) + ) : null} submitChanges({ parent: null })} diff --git a/apps/app/components/issues/parent-issues-list-modal.tsx b/apps/app/components/issues/parent-issues-list-modal.tsx index 65c67616e..62091511a 100644 --- a/apps/app/components/issues/parent-issues-list-modal.tsx +++ b/apps/app/components/issues/parent-issues-list-modal.tsx @@ -11,8 +11,9 @@ import useDebounce from "hooks/use-debounce"; // components import { LayerDiagonalIcon } from "components/icons"; // ui -import { Loader } from "components/ui"; +import { Loader, ToggleSwitch, Tooltip } from "components/ui"; // icons +import { LaunchOutlined } from "@mui/icons-material"; import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; // types import { ISearchIssueResponse } from "types"; @@ -37,6 +38,7 @@ export const ParentIssuesListModal: React.FC = ({ const [searchTerm, setSearchTerm] = useState(""); const [issues, setIssues] = useState([]); const [isSearching, setIsSearching] = useState(false); + const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); const debouncedSearchTerm: string = useDebounce(searchTerm, 500); @@ -46,6 +48,7 @@ export const ParentIssuesListModal: React.FC = ({ const handleClose = () => { onClose(); setSearchTerm(""); + setIsWorkspaceLevel(false); }; useEffect(() => { @@ -58,10 +61,11 @@ export const ParentIssuesListModal: React.FC = ({ search: debouncedSearchTerm, parent: true, issue_id: issueId, + workspace_search: isWorkspaceLevel, }) .then((res) => setIssues(res)) .finally(() => setIsSearching(false)); - }, [debouncedSearchTerm, isOpen, issueId, projectId, workspaceSlug]); + }, [debouncedSearchTerm, isOpen, issueId, isWorkspaceLevel, projectId, workspaceSlug]); return ( <> @@ -115,7 +119,29 @@ export const ParentIssuesListModal: React.FC = ({ displayValue={() => ""} />
- +
+ +
+ setIsWorkspaceLevel((prevData) => !prevData)} + label="Workspace level" + /> + +
+
+
+ {searchTerm !== "" && (
Search results for{" "} @@ -158,12 +184,12 @@ export const ParentIssuesListModal: React.FC = ({ key={issue.id} value={issue} className={({ active, selected }) => - `flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${ + `group flex items-center justify-between gap-2 cursor-pointer select-none rounded-md px-3 py-2 text-custom-text-200 ${ active ? "bg-custom-background-80 text-custom-text-100" : "" } ${selected ? "text-custom-text-100" : ""}` } > - <> +
= ({ {issue.project__identifier}-{issue.sequence_id} {" "} {issue.name} - +
+
e.stopPropagation()} + > + + ))} diff --git a/apps/app/components/issues/sidebar-select/blocked.tsx b/apps/app/components/issues/sidebar-select/blocked.tsx index 792482736..315140ea9 100644 --- a/apps/app/components/issues/sidebar-select/blocked.tsx +++ b/apps/app/components/issues/sidebar-select/blocked.tsx @@ -1,20 +1,18 @@ import React, { useState } from "react"; -import Link from "next/link"; import { useRouter } from "next/router"; // react-hook-form import { UseFormWatch } from "react-hook-form"; // hooks import useToast from "hooks/use-toast"; -import useProjectDetails from "hooks/use-project-details"; // components import { ExistingIssuesListModal } from "components/core"; // icons import { XMarkIcon } from "@heroicons/react/24/outline"; import { BlockedIcon } from "components/icons"; // types -import { BlockeIssue, IIssue, ISearchIssueResponse, UserAuth } from "types"; +import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; type Props = { issueId?: string; @@ -34,10 +32,9 @@ export const SidebarBlockedSelect: React.FC = ({ const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); const { setToastAlert } = useToast(); - const { projectDetails } = useProjectDetails(); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug } = router.query; const handleClose = () => { setIsBlockedModalOpen(false); @@ -54,11 +51,16 @@ export const SidebarBlockedSelect: React.FC = ({ return; } - const selectedIssues: BlockeIssue[] = data.map((i) => ({ + const selectedIssues: { blocked_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({ blocked_issue_detail: { id: i.id, name: i.name, sequence_id: i.sequence_id, + project_detail: { + id: i.project_id, + identifier: i.project__identifier, + name: i.project__name, + }, }, })); @@ -94,14 +96,15 @@ export const SidebarBlockedSelect: React.FC = ({ key={issue.blocked_issue_detail?.id} className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20" > - - - - {`${projectDetails?.identifier}-${issue.blocked_issue_detail?.sequence_id}`} - - + + {`${issue.blocked_issue_detail?.project_detail.identifier}-${issue.blocked_issue_detail?.sequence_id}`} + -
- + setIsWorkspaceLevel((prevData) => !prevData)} + /> + +
+ + )}
diff --git a/apps/app/components/issues/sidebar-select/blocked.tsx b/apps/app/components/issues/sidebar-select/blocked.tsx index 315140ea9..76373700c 100644 --- a/apps/app/components/issues/sidebar-select/blocked.tsx +++ b/apps/app/components/issues/sidebar-select/blocked.tsx @@ -82,6 +82,7 @@ export const SidebarBlockedSelect: React.FC = ({ handleClose={() => setIsBlockedModalOpen(false)} searchParams={{ blocker_blocked_by: true, issue_id: issueId }} handleOnSubmit={onSubmit} + workspaceLevelToggle />
diff --git a/apps/app/components/issues/sidebar-select/blocker.tsx b/apps/app/components/issues/sidebar-select/blocker.tsx index 7ec664700..c25adc49e 100644 --- a/apps/app/components/issues/sidebar-select/blocker.tsx +++ b/apps/app/components/issues/sidebar-select/blocker.tsx @@ -82,6 +82,7 @@ export const SidebarBlockerSelect: React.FC = ({ handleClose={() => setIsBlockerModalOpen(false)} searchParams={{ blocker_blocked_by: true, issue_id: issueId }} handleOnSubmit={onSubmit} + workspaceLevelToggle />
diff --git a/apps/app/components/issues/sub-issues-list.tsx b/apps/app/components/issues/sub-issues-list.tsx index 9c85ef546..7d526d661 100644 --- a/apps/app/components/issues/sub-issues-list.tsx +++ b/apps/app/components/issues/sub-issues-list.tsx @@ -115,6 +115,7 @@ export const SubIssuesList: FC = ({ parentIssue, user, disabled = false } handleClose={() => setSubIssuesListModal(false)} searchParams={{ sub_issue: true, issue_id: parentIssue?.id }} handleOnSubmit={addAsSubIssue} + workspaceLevelToggle /> {subIssuesResponse && subIssuesResponse.sub_issues.length > 0 ? ( diff --git a/apps/app/components/ui/empty-state.tsx b/apps/app/components/ui/empty-state.tsx index 5af3bf548..a13e59679 100644 --- a/apps/app/components/ui/empty-state.tsx +++ b/apps/app/components/ui/empty-state.tsx @@ -11,6 +11,7 @@ type Props = { image: any; buttonText?: string; buttonIcon?: any; + secondaryButton?: React.ReactNode; onClick?: () => void; isFullScreen?: boolean; }; @@ -22,6 +23,7 @@ export const EmptyState: React.FC = ({ onClick, buttonText, buttonIcon, + secondaryButton, isFullScreen = true, }) => (
= ({ {buttonText}
{title}

{description}

- {buttonText && ( - - {buttonIcon} - {buttonText} - - )} +
+ {buttonText && ( + + {buttonIcon} + {buttonText} + + )} + {secondaryButton} +
); From fdb7da4d453e3ea1dc1bd1aeacd25082299533fe Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sun, 23 Jul 2023 22:10:40 +0530 Subject: [PATCH 06/99] chore: show proper error messages on profile form submit (#1611) --- .../[workspaceSlug]/me/profile/index.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx index 85834d546..3fb73c40d 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx @@ -29,11 +29,10 @@ const defaultValues: Partial = { first_name: "", last_name: "", email: "", - role: "", + role: "Product / Project Manager", }; const Profile: NextPage = () => { - const [isEditing, setIsEditing] = useState(false); const [isRemoving, setIsRemoving] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); @@ -55,6 +54,16 @@ const Profile: NextPage = () => { }, [myProfile, reset]); const onSubmit = async (formData: IUser) => { + if (formData.first_name === "" || formData.last_name === "") { + setToastAlert({ + type: "error", + title: "Error!", + message: "First and last names are required.", + }); + + return; + } + const payload: Partial = { first_name: formData.first_name, last_name: formData.last_name, @@ -67,9 +76,9 @@ const Profile: NextPage = () => { .then((res) => { mutateUser((prevData) => { if (!prevData) return prevData; + return { ...prevData, ...res }; }, false); - setIsEditing(false); setToastAlert({ type: "success", title: "Success!", @@ -146,7 +155,7 @@ const Profile: NextPage = () => {
-
+

Profile Picture

@@ -207,9 +216,6 @@ const Profile: NextPage = () => { error={errors.first_name} placeholder="Enter your first name" autoComplete="off" - validations={{ - required: "This field is required.", - }} /> { value={value} onChange={onChange} label={value ? value.toString() : "Select your role"} + buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : ""} width="w-full" input position="right" @@ -267,14 +274,15 @@ const Profile: NextPage = () => { )} /> + {errors.role && Please select a role}
- + {isSubmitting ? "Updating..." : "Update profile"}
-
+
) : (
From 7669ee87557bf20f6ff178c5a50e8617c20cdd76 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sun, 23 Jul 2023 22:12:13 +0530 Subject: [PATCH 07/99] fix: project select not working on the create issue modal (#1608) --- .../components/issues/description-form.tsx | 95 ++++++++----------- apps/app/components/issues/form.tsx | 6 +- apps/app/components/issues/modal.tsx | 4 +- apps/app/components/issues/select/project.tsx | 12 +-- 4 files changed, 50 insertions(+), 67 deletions(-) diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index 0770834fa..e81c8c1b3 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -96,13 +96,7 @@ export const IssueDescriptionForm: FC = ({ setCharacterLimit(false); setIsSubmitting(true); - handleSubmit(handleDescriptionFormSubmit)() - .then(() => { - setIsSubmitting(false); - }) - .catch(() => { - setIsSubmitting(false); - }); + handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting(false)); }} required={true} className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-xl outline-none ring-0 focus:ring-1 focus:ring-custom-primary" @@ -110,7 +104,7 @@ export const IssueDescriptionForm: FC = ({ disabled={!isAllowed} /> {characterLimit && ( -
+
255 ? "text-red-500" : "" @@ -123,52 +117,47 @@ export const IssueDescriptionForm: FC = ({ )}
{errors.name ? errors.name.message : null} - { - if (!value && !watch("description_html")) return <>; +
+ { + if (!value && !watch("description_html")) return <>; - return ( - { - setShowAlert(true); - setValue("description", jsonValue); - }} - onHTMLChange={(htmlValue) => { - setShowAlert(true); - setValue("description_html", htmlValue); - }} - onBlur={() => { - setIsSubmitting(true); - handleSubmit(handleDescriptionFormSubmit)() - .then(() => { - setIsSubmitting(false); - setShowAlert(false); - }) - .catch(() => { - setIsSubmitting(false); - }); - }} - placeholder="Description" - editable={isAllowed} - /> - ); - }} - /> -
- Saving... + return ( + { + setShowAlert(true); + setValue("description", jsonValue); + }} + onHTMLChange={(htmlValue) => { + setShowAlert(true); + setValue("description_html", htmlValue); + }} + onBlur={() => { + setIsSubmitting(true); + handleSubmit(handleDescriptionFormSubmit)() + .then(() => setShowAlert(false)) + .finally(() => setIsSubmitting(false)); + }} + placeholder="Description" + editable={isAllowed} + /> + ); + }} + /> + {isSubmitting && ( +
+ Saving... +
+ )}
); diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 2a17a07a8..8469819e8 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -261,8 +261,10 @@ export const IssueForm: FC = ({ render={({ field: { value, onChange } }) => ( { + onChange(val); + setActiveProject(val); + }} /> )} /> diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index 09c4dbfe0..8ea57a439 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -95,9 +95,9 @@ export const CreateUpdateIssueModal: React.FC = ({ }; useEffect(() => { - if (projects && projects.length > 0) + if (projects && projects.length > 0 && !activeProject) setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null); - }, [projectId, projects]); + }, [activeProject, projectId, projects]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { diff --git a/apps/app/components/issues/select/project.tsx b/apps/app/components/issues/select/project.tsx index 39fd12491..5d1c964e1 100644 --- a/apps/app/components/issues/select/project.tsx +++ b/apps/app/components/issues/select/project.tsx @@ -8,14 +8,9 @@ import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; export interface IssueProjectSelectProps { value: string; onChange: (value: string) => void; - setActiveProject: React.Dispatch>; } -export const IssueProjectSelect: React.FC = ({ - value, - onChange, - setActiveProject, -}) => { +export const IssueProjectSelect: React.FC = ({ value, onChange }) => { const { projects } = useProjects(); return ( @@ -29,10 +24,7 @@ export const IssueProjectSelect: React.FC = ({ } - onChange={(val: string) => { - onChange(val); - setActiveProject(val); - }} + onChange={(val: string) => onChange(val)} noChevron > {projects ? ( From ccbcfecc6d586d174e741d47920682e99cdbea90 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sun, 23 Jul 2023 22:13:09 +0530 Subject: [PATCH 08/99] fix: form not submitting on enter (#1613) --- .../app/components/core/modals/link-modal.tsx | 2 +- .../components/core/sidebar/links-list.tsx | 60 +++++++++---------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/apps/app/components/core/modals/link-modal.tsx b/apps/app/components/core/modals/link-modal.tsx index b8d512a85..63a821dce 100644 --- a/apps/app/components/core/modals/link-modal.tsx +++ b/apps/app/components/core/modals/link-modal.tsx @@ -116,7 +116,7 @@ export const LinkModal: React.FC = ({ isOpen, handleClose, onFormSubmit }
Cancel - + {isSubmitting ? "Adding Link..." : "Add Link"}
diff --git a/apps/app/components/core/sidebar/links-list.tsx b/apps/app/components/core/sidebar/links-list.tsx index a0619b924..0d8a97298 100644 --- a/apps/app/components/core/sidebar/links-list.tsx +++ b/apps/app/components/core/sidebar/links-list.tsx @@ -1,5 +1,3 @@ -import Link from "next/link"; - // icons import { ArrowTopRightOnSquareIcon, LinkIcon, TrashIcon } from "@heroicons/react/24/outline"; // helpers @@ -30,14 +28,14 @@ export const LinksList: React.FC = ({ links, handleDeleteLink, userAuth } ))} From a7b5ad55ab63f2be598d39b6e40e7bfeb875b95c Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sun, 23 Jul 2023 22:14:26 +0530 Subject: [PATCH 09/99] style: create project modal (#1628) * style: create project modal * fix: build error * fix: modal width --- .../analytics/custom-analytics/sidebar.tsx | 7 +- .../components/core/image-picker-popover.tsx | 4 +- .../components/emoji-icon-picker/index.tsx | 17 +- .../components/emoji-icon-picker/types.d.ts | 2 +- .../integration/jira/import-users.tsx | 2 +- .../app/components/issues/select/assignee.tsx | 1 - .../modules/sidebar-select/select-members.tsx | 2 +- .../project/create-project-modal.tsx | 350 +++++++++++------- .../project/delete-project-modal.tsx | 22 +- .../project/single-project-card.tsx | 2 +- .../ui/dropdowns/custom-search-select.tsx | 14 +- .../components/ui/dropdowns/custom-select.tsx | 2 +- apps/app/components/ui/text-area/index.tsx | 4 +- apps/app/constants/project.ts | 13 +- apps/app/hooks/use-workspace-members.tsx | 2 +- .../projects/[projectId]/settings/control.tsx | 4 +- .../projects/[projectId]/settings/index.tsx | 14 +- apps/app/types/projects.d.ts | 2 +- 18 files changed, 273 insertions(+), 191 deletions(-) diff --git a/apps/app/components/analytics/custom-analytics/sidebar.tsx b/apps/app/components/analytics/custom-analytics/sidebar.tsx index fbef3913c..6d413a9c9 100644 --- a/apps/app/components/analytics/custom-analytics/sidebar.tsx +++ b/apps/app/components/analytics/custom-analytics/sidebar.tsx @@ -360,11 +360,8 @@ export const AnalyticsSidebar: React.FC = ({
Network
- { - NETWORK_CHOICES[ - `${projectDetails?.network}` as keyof typeof NETWORK_CHOICES - ] - } + {NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ?? + ""}
diff --git a/apps/app/components/core/image-picker-popover.tsx b/apps/app/components/core/image-picker-popover.tsx index 1611f6f82..9304f9ba6 100644 --- a/apps/app/components/core/image-picker-popover.tsx +++ b/apps/app/components/core/image-picker-popover.tsx @@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC = ({ label, value, onChange }) return ( setIsOpen((prev) => !prev)} > {label} @@ -77,7 +77,7 @@ export const ImagePickerPopover: React.FC = ({ label, value, onChange }) leaveTo="transform opacity-0 scale-95" > -
+
{tabOptions.map((tab) => ( diff --git a/apps/app/components/emoji-icon-picker/index.tsx b/apps/app/components/emoji-icon-picker/index.tsx index 61ba80843..ffe8b33d6 100644 --- a/apps/app/components/emoji-icon-picker/index.tsx +++ b/apps/app/components/emoji-icon-picker/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; // headless ui import { Tab, Transition, Popover } from "@headlessui/react"; // react colors @@ -11,8 +11,6 @@ import icons from "./icons.json"; // helpers import { getRecentEmojis, saveRecentEmoji } from "./helpers"; import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; -// hooks -import useOutsideClickDetector from "hooks/use-outside-click-detector"; const tabOptions = [ { @@ -26,8 +24,6 @@ const tabOptions = [ ]; const EmojiIconPicker: React.FC = ({ label, value, onChange, onIconColorChange }) => { - const ref = useRef(null); - const [isOpen, setIsOpen] = useState(false); const [openColorPicker, setOpenColorPicker] = useState(false); const [activeColor, setActiveColor] = useState("rgb(var(--color-text-200))"); @@ -38,20 +34,13 @@ const EmojiIconPicker: React.FC = ({ label, value, onChange, onIconColorC setRecentEmojis(getRecentEmojis()); }, []); - useOutsideClickDetector(ref, () => { - setIsOpen(false); - }); - useEffect(() => { if (!value || value?.length === 0) onChange(getRandomEmoji()); }, [value, onChange]); return ( - - setIsOpen((prev) => !prev)} - > + + setIsOpen((prev) => !prev)} className="outline-none"> {label} { const router = useRouter(); const { workspaceSlug } = router.query; - const { workspaceMembers: members } = useWorkspaceMembers(workspaceSlug?.toString()); + const { workspaceMembers: members } = useWorkspaceMembers(workspaceSlug?.toString() ?? ""); const options = members?.map((member) => ({ value: member.member.email, diff --git a/apps/app/components/issues/select/assignee.tsx b/apps/app/components/issues/select/assignee.tsx index 6805c931e..47fe07c42 100644 --- a/apps/app/components/issues/select/assignee.tsx +++ b/apps/app/components/issues/select/assignee.tsx @@ -21,7 +21,6 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on const router = useRouter(); const { workspaceSlug } = router.query; - // fetching project members const { data: members } = useSWR( workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, workspaceSlug && projectId diff --git a/apps/app/components/modules/sidebar-select/select-members.tsx b/apps/app/components/modules/sidebar-select/select-members.tsx index c6bcb9ec8..af530503a 100644 --- a/apps/app/components/modules/sidebar-select/select-members.tsx +++ b/apps/app/components/modules/sidebar-select/select-members.tsx @@ -55,7 +55,7 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => {
{value && value.length > 0 && Array.isArray(value) ? ( diff --git a/apps/app/components/project/create-project-modal.tsx b/apps/app/components/project/create-project-modal.tsx index e97451a02..d9c6c24d3 100644 --- a/apps/app/components/project/create-project-modal.tsx +++ b/apps/app/components/project/create-project-modal.tsx @@ -2,19 +2,29 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; +import { mutate } from "swr"; +// react-hook-form import { useForm, Controller } from "react-hook-form"; - +// headless ui import { Dialog, Transition } from "@headlessui/react"; - // services import projectServices from "services/project.service"; -import workspaceService from "services/workspace.service"; // hooks import useToast from "hooks/use-toast"; +import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; +import useWorkspaceMembers from "hooks/use-workspace-members"; // ui -import { Input, TextArea, CustomSelect, PrimaryButton, SecondaryButton } from "components/ui"; +import { + Input, + TextArea, + CustomSelect, + PrimaryButton, + SecondaryButton, + Icon, + Avatar, + CustomSearchSelect, +} from "components/ui"; // icons import { XMarkIcon } from "@heroicons/react/24/outline"; // components @@ -25,7 +35,7 @@ import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; // types import { ICurrentUserResponse, IProject } from "types"; // fetch-keys -import { PROJECTS_LIST, WORKSPACE_MEMBERS_ME } from "constants/fetch-keys"; +import { PROJECTS_LIST } from "constants/fetch-keys"; // constants import { NETWORK_CHOICES } from "constants/project"; @@ -36,13 +46,14 @@ type Props = { }; const defaultValues: Partial = { - name: "", - identifier: "", - description: "", - network: 2, - emoji_and_icon: getRandomEmoji(), cover_image: "https://images.unsplash.com/photo-1575116464504-9e7652fddcb3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyODUyNTV8MHwxfHNlYXJjaHwxOHx8cGxhbmV8ZW58MHx8fHwxNjgxNDY4NTY5&ixlib=rb-4.0.3&q=80&w=1080", + description: "", + emoji_and_icon: getRandomEmoji(), + identifier: "", + name: "", + network: 2, + project_lead: null, }; const IsGuestCondition: React.FC<{ @@ -52,6 +63,7 @@ const IsGuestCondition: React.FC<{ useEffect(() => { setIsOpen(false); + setToastAlert({ title: "Error", type: "error", @@ -62,31 +74,22 @@ const IsGuestCondition: React.FC<{ return null; }; -export const CreateProjectModal: React.FC = (props) => { - const { isOpen, setIsOpen, user } = props; - +export const CreateProjectModal: React.FC = ({ isOpen, setIsOpen, user }) => { const [isChangeIdentifierRequired, setIsChangeIdentifierRequired] = useState(true); const { setToastAlert } = useToast(); - const { - query: { workspaceSlug }, - } = useRouter(); + const router = useRouter(); + const { workspaceSlug } = router.query; - const { data: myWorkspaceMembership } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null, - workspaceSlug ? () => workspaceService.workspaceMemberMe(workspaceSlug as string) : null, - { - shouldRetryOnError: false, - } - ); + const { memberDetails } = useWorkspaceMyMembership(); + const { workspaceMembers } = useWorkspaceMembers(workspaceSlug?.toString() ?? ""); const { register, formState: { errors, isSubmitting }, handleSubmit, reset, - setError, control, watch, setValue, @@ -96,8 +99,8 @@ export const CreateProjectModal: React.FC = (props) => { reValidateMode: "onChange", }); - const projectName = watch("name") ?? ""; - const projectIdentifier = watch("identifier") ?? ""; + const projectName = watch("name"); + const projectIdentifier = watch("identifier"); useEffect(() => { if (projectName && isChangeIdentifierRequired) @@ -120,10 +123,10 @@ export const CreateProjectModal: React.FC = (props) => { else payload.emoji = formData.emoji_and_icon; await projectServices - .createProject(workspaceSlug as string, payload, user) + .createProject(workspaceSlug.toString(), payload, user) .then((res) => { mutate( - PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), + PROJECTS_LIST(workspaceSlug.toString(), { is_favorite: "all" }), (prevData) => [res, ...(prevData ?? [])], false ); @@ -135,28 +138,40 @@ export const CreateProjectModal: React.FC = (props) => { handleClose(); }) .catch((err) => { - if (err.status === 403) { + Object.keys(err.data).map((key) => setToastAlert({ type: "error", title: "Error!", - message: "You don't have permission to create project.", - }); - handleClose(); - return; - } - err = err.data; - Object.keys(err).map((key) => { - const errorMessages = err[key]; - setError(key as keyof IProject, { - message: Array.isArray(errorMessages) ? errorMessages.join(", ") : errorMessages, - }); - }); + message: err.data[key], + }) + ); }); }; - if (myWorkspaceMembership && isOpen) { - if (myWorkspaceMembership.role <= 10) return ; - } + const options = workspaceMembers?.map((member) => ({ + value: member.member.id, + query: + (member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email) + + " " + + member.member.last_name ?? "", + content: ( +
+ + {`${ + member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email + } ${member.member.last_name ?? ""}`} +
+ ), + })); + + const currentNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network")); + + if (memberDetails && isOpen) + if (memberDetails.role <= 10) return ; return ( @@ -184,12 +199,12 @@ export const CreateProjectModal: React.FC = (props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
+ +
{watch("cover_image") !== null && ( Cover Image )} @@ -199,87 +214,85 @@ export const CreateProjectModal: React.FC = (props) => {
-
-
-

Create Project

-
- { - setValue("cover_image", image); - }} - value={watch("cover_image")} - /> -
+
+ { + setValue("cover_image", image); + }} + value={watch("cover_image")} + /> +
+
+ ( + + {value ? ( + typeof value === "object" ? ( + + {value.name} + + ) : ( + renderEmoji(value) + ) + ) : ( + "Icon" + )} +
+ } + onChange={onChange} + value={value} + /> + )} + />
-
-
-
-
- ( - - {value.name} - - ) : ( - renderEmoji(value) - ) - ) : ( - "Icon" - ) - } - onChange={onChange} - value={value} - /> - )} - /> -
- -
+ +
+
+
-
- -
-
+
setIsChangeIdentifierRequired(false)} validations={{ required: "Identifier is required", validate: (value) => - /^[A-Z]+$/.test(value) || "Identifier must be uppercase text.", + /^[A-Z]+$/.test(value) || "Identifier must be in uppercase.", minLength: { value: 1, message: "Identifier must at least be of 1 character", @@ -291,47 +304,110 @@ export const CreateProjectModal: React.FC = (props) => { }} />
- ( - k === value.toString()) - ? NETWORK_CHOICES[value.toString() as keyof typeof NETWORK_CHOICES] - : "Select network" - } - width="w-full" - input - > - {Object.keys(NETWORK_CHOICES).map((key) => ( - - {NETWORK_CHOICES[key as keyof typeof NETWORK_CHOICES]} - - ))} - - )} - /> +
+