From 74348009990896331593d81d7d88967ebc0cf366 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:09:53 +0530 Subject: [PATCH 01/82] chore: show message if dragging unjoined project (#1763) --- .../project/single-sidebar-project.tsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/app/components/project/single-sidebar-project.tsx b/apps/app/components/project/single-sidebar-project.tsx index acc1f493e..ef1b6771e 100644 --- a/apps/app/components/project/single-sidebar-project.tsx +++ b/apps/app/components/project/single-sidebar-project.tsx @@ -137,21 +137,30 @@ export const SingleSidebarProject: React.FC = ({ {({ open }) => ( <>
{provided && ( - + + )} Date: Wed, 2 Aug 2023 13:38:45 +0530 Subject: [PATCH 02/82] fix: invalid project selection in create issue modal (#1766) --- apps/app/components/issues/modal.tsx | 32 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index be3b556a1..d7180b761 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { useRouter } from "next/router"; @@ -98,20 +98,36 @@ export const CreateUpdateIssueModal: React.FC = ({ assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""], }; + const onClose = useCallback(() => { + handleClose(); + setActiveProject(null); + }, [handleClose]); + useEffect(() => { + // if modal is closed, reset active project to null + // and return to avoid activeProject being set to some other project + if (!isOpen) { + setActiveProject(null); + return; + } + + // if data is present, set active project to the project of the + // issue. This has more priority than the project in the url. if (data && data.project) { setActiveProject(data.project); return; } + // if data is not present, set active project to the project + // in the url. This has the least priority. if (projects && projects.length > 0 && !activeProject) setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null); - }, [activeProject, data, projectId, projects]); + }, [activeProject, data, projectId, projects, isOpen]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { - handleClose(); + onClose(); } }; @@ -119,7 +135,7 @@ export const CreateUpdateIssueModal: React.FC = ({ return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, [handleClose]); + }, [onClose]); const addIssueToCycle = async (issueId: string, cycleId: string) => { if (!workspaceSlug || !activeProject) return; @@ -267,7 +283,7 @@ export const CreateUpdateIssueModal: React.FC = ({ }); }); - if (!createMore) handleClose(); + if (!createMore) onClose(); }; const updateIssue = async (payload: Partial) => { @@ -286,7 +302,7 @@ export const CreateUpdateIssueModal: React.FC = ({ if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle); if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module); - if (!createMore) handleClose(); + if (!createMore) onClose(); setToastAlert({ type: "success", @@ -324,7 +340,7 @@ export const CreateUpdateIssueModal: React.FC = ({ return ( - handleClose()}> + = ({ initialData={data ?? prePopulateData} createMore={createMore} setCreateMore={setCreateMore} - handleClose={handleClose} + handleClose={onClose} projectId={activeProject ?? ""} setActiveProject={setActiveProject} status={data ? true : false} From 584192faba6297bc006573c5c47038543230ab20 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:21:26 +0530 Subject: [PATCH 03/82] style: sidebar project list improvement (#1767) --- apps/app/components/project/sidebar-list.tsx | 2 +- .../project/single-sidebar-project.tsx | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/app/components/project/sidebar-list.tsx b/apps/app/components/project/sidebar-list.tsx index 5790e7c0b..1bb3711fc 100644 --- a/apps/app/components/project/sidebar-list.tsx +++ b/apps/app/components/project/sidebar-list.tsx @@ -130,7 +130,7 @@ export const ProjectSidebarList: FC = () => { data={projectToDelete} user={user} /> -
+
{(provided) => ( diff --git a/apps/app/components/project/single-sidebar-project.tsx b/apps/app/components/project/single-sidebar-project.tsx index ef1b6771e..8545739f7 100644 --- a/apps/app/components/project/single-sidebar-project.tsx +++ b/apps/app/components/project/single-sidebar-project.tsx @@ -170,17 +170,17 @@ export const SingleSidebarProject: React.FC = ({ > -
+
{project.emoji ? ( {renderEmoji(project.emoji)} ) : project.icon_prop ? ( -
+
{renderEmoji(project.icon_prop)}
) : ( @@ -190,19 +190,15 @@ export const SingleSidebarProject: React.FC = ({ )} {!sidebarCollapse && ( -

- {truncateText(project.name, 15)} +

+ {project.name}

)}
{!sidebarCollapse && ( @@ -211,7 +207,7 @@ export const SingleSidebarProject: React.FC = ({ {!sidebarCollapse && ( - + {!shortContextMenu && ( From 87a920174e2705fbe92f3c33eca4f6237052dd9c Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:21:48 +0530 Subject: [PATCH 04/82] fix: comment reaction mutation (#1768) --- apps/app/hooks/use-comment-reaction.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/app/hooks/use-comment-reaction.tsx b/apps/app/hooks/use-comment-reaction.tsx index 16469f66a..6deb4d9fb 100644 --- a/apps/app/hooks/use-comment-reaction.tsx +++ b/apps/app/hooks/use-comment-reaction.tsx @@ -70,7 +70,8 @@ const useCommentReaction = ( mutateCommentReactions( (prevData) => - prevData?.filter((r) => r.actor !== user?.user?.id || r.reaction !== reaction) || [] + prevData?.filter((r) => r.actor !== user?.user?.id || r.reaction !== reaction) || [], + false ); await reactionService.deleteIssueCommentReaction( From a66dcb94198343aadcaeadec4636b8d82abfab4d Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:42:24 +0530 Subject: [PATCH 05/82] fix: user profiles n plus 1 (#1765) --- apiserver/plane/api/views/workspace.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index b195cedb1..a862c0b4c 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -75,6 +75,7 @@ from plane.db.models import ( Label, WorkspaceMember, CycleIssue, + IssueReaction, ) from plane.api.permissions import ( WorkSpaceBasePermission, @@ -1321,6 +1322,12 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): ) .select_related("project", "workspace", "state", "parent") .prefetch_related("assignees", "labels") + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related("actor"), + ) + ) .order_by("-created_at") .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) From 9a298962917beb72ca60ccae746d2a1769afd7df Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:42:47 +0530 Subject: [PATCH 06/82] fix: bulk issue import (#1773) --- apiserver/plane/api/views/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/api/views/importer.py index 63e2d38a1..4fc7ad483 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/api/views/importer.py @@ -332,7 +332,7 @@ class BulkImportIssuesEndpoint(BaseAPIView): # if there is no default state assign any random state if default_state is None: default_state = State.objects.filter( - ~Q(name="Triage"), sproject_id=project_id + ~Q(name="Triage"), project_id=project_id ).first() # Get the maximum sequence_id From c16b0daa22459086be480dcd4d9dde31ca8274f8 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:45:34 +0530 Subject: [PATCH 07/82] style: profile activity (#1771) * style: profile activity comment log styling * chore: profile feed activity refactor * style: sidebar project list --- apps/app/components/core/feeds.tsx | 342 ------------------ apps/app/components/core/index.ts | 1 - .../project/single-sidebar-project.tsx | 8 +- .../[workspaceSlug]/me/profile/activity.tsx | 207 ++++++++++- 4 files changed, 200 insertions(+), 358 deletions(-) delete mode 100644 apps/app/components/core/feeds.tsx diff --git a/apps/app/components/core/feeds.tsx b/apps/app/components/core/feeds.tsx deleted file mode 100644 index 2924ec456..000000000 --- a/apps/app/components/core/feeds.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import React from "react"; - -import { useRouter } from "next/router"; - -import Link from "next/link"; - -// icons -import { - ArrowTopRightOnSquareIcon, - ChatBubbleLeftEllipsisIcon, - Squares2X2Icon, -} from "@heroicons/react/24/outline"; -import { BlockedIcon, BlockerIcon } from "components/icons"; -import { Icon } from "components/ui"; -// helpers -import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper"; -import { addSpaceIfCamelCase } from "helpers/string.helper"; -// types -import RemirrorRichTextEditor from "components/rich-text-editor"; - -const activityDetails: { - [key: string]: { - message?: string; - icon: JSX.Element; - }; -} = { - assignee: { - message: "removed the assignee", - icon:
From a1ae338c3719213d5b6b6d5e7b3f30d20972fff2 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:47:36 +0530 Subject: [PATCH 08/82] chore: add non existing states for project entities (#1770) --- apps/app/components/ui/empty-state.tsx | 4 +- .../projects/[projectId]/cycles/[cycleId].tsx | 79 ++++++++++++------- .../projects/[projectId]/issues/[issueId].tsx | 22 +++++- .../[projectId]/modules/[moduleId].tsx | 53 ++++++++----- .../projects/[projectId]/pages/[pageId].tsx | 69 +++++++++------- .../projects/[projectId]/views/[viewId].tsx | 24 ++++-- 6 files changed, 164 insertions(+), 87 deletions(-) diff --git a/apps/app/components/ui/empty-state.tsx b/apps/app/components/ui/empty-state.tsx index 26b621d98..098c3f152 100644 --- a/apps/app/components/ui/empty-state.tsx +++ b/apps/app/components/ui/empty-state.tsx @@ -7,7 +7,7 @@ import { PrimaryButton } from "components/ui"; type Props = { title: string; - description: React.ReactNode | string; + description?: React.ReactNode; image: any; primaryButton?: { icon?: any; @@ -34,7 +34,7 @@ export const EmptyState: React.FC = ({
{primaryButton?.text}
{title}
-

{description}

+ {description &&

{description}

}
{primaryButton && ( diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index 0da8f797c..de6ad561e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -22,8 +22,10 @@ import useUserAuth from "hooks/use-user-auth"; // components import { AnalyticsProjectModal } from "components/analytics"; // ui -import { CustomMenu, SecondaryButton } from "components/ui"; +import { CustomMenu, EmptyState, SecondaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +// images +import emptyCycle from "public/empty-state/cycle.svg"; // helpers import { truncateText } from "helpers/string.helper"; import { getDateRangeStatus } from "helpers/date-time.helper"; @@ -52,14 +54,14 @@ const SingleCycle: React.FC = () => { : null ); - const { data: cycleDetails } = useSWR( - cycleId ? CYCLE_DETAILS(cycleId as string) : null, + const { data: cycleDetails, error } = useSWR( + workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null, workspaceSlug && projectId && cycleId ? () => cycleServices.getCycleDetails( - workspaceSlug as string, - projectId as string, - cycleId as string + workspaceSlug.toString(), + projectId.toString(), + cycleId.toString() ) : null ); @@ -159,31 +161,48 @@ const SingleCycle: React.FC = () => {
} > - setTransferIssuesModal(false)} - isOpen={transferIssuesModal} - /> - setAnalyticsModal(false)} /> -
- {cycleStatus === "completed" && ( - setTransferIssuesModal(true)} /> - )} - router.push(`/${workspaceSlug}/projects/${projectId}/cycles`), + }} /> -
- + ) : ( + <> + setTransferIssuesModal(false)} + isOpen={transferIssuesModal} + /> + setAnalyticsModal(false)} + /> +
+ {cycleStatus === "completed" && ( + setTransferIssuesModal(true)} /> + )} + +
+ + + )} ); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index d5f7c8ec6..35697ee0f 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -15,8 +15,10 @@ import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // components import { IssueDetailsSidebar, IssueMainContent } from "components/issues"; // ui -import { Loader } from "components/ui"; +import { EmptyState, Loader } from "components/ui"; import { Breadcrumbs } from "components/breadcrumbs"; +// images +import emptyIssue from "public/empty-state/issue.svg"; // types import { IIssue } from "types"; import type { NextPage } from "next"; @@ -45,7 +47,11 @@ const IssueDetailsPage: NextPage = () => { const { user } = useUserAuth(); - const { data: issueDetails, mutate: mutateIssueDetails } = useSWR( + const { + data: issueDetails, + mutate: mutateIssueDetails, + error, + } = useSWR( workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null, workspaceSlug && projectId && issueId ? () => @@ -125,7 +131,17 @@ const IssueDetailsPage: NextPage = () => { } > - {issueDetails && projectId ? ( + {error ? ( + router.push(`/${workspaceSlug}/projects/${projectId}/issues`), + }} + /> + ) : issueDetails && projectId ? (
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index 71c3bb655..573192626 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -20,8 +20,10 @@ import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "component import { ModuleDetailsSidebar } from "components/modules"; import { AnalyticsProjectModal } from "components/analytics"; // ui -import { CustomMenu, SecondaryButton } from "components/ui"; +import { CustomMenu, EmptyState, SecondaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +// images +import emptyModule from "public/empty-state/module.svg"; // helpers import { truncateText } from "helpers/string.helper"; // types @@ -60,7 +62,7 @@ const SingleModule: React.FC = () => { : null ); - const { data: moduleDetails } = useSWR( + const { data: moduleDetails, error } = useSWR( moduleId ? MODULE_DETAILS(moduleId as string) : null, workspaceSlug && projectId ? () => @@ -162,22 +164,37 @@ const SingleModule: React.FC = () => {
} > - setAnalyticsModal(false)} /> - -
- -
- - + {error ? ( + router.push(`/${workspaceSlug}/projects/${projectId}/modules`), + }} + /> + ) : ( + <> + setAnalyticsModal(false)} + /> +
+ +
+ + + )} ); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index b26be661e..ed52f447a 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useRouter } from "next/router"; @@ -28,7 +28,16 @@ import { CreateLabelModal } from "components/labels"; import { CreateBlock } from "components/pages/create-block"; // ui import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; -import { CustomSearchSelect, Loader, TextArea, ToggleSwitch, Tooltip } from "components/ui"; +import { + CustomSearchSelect, + EmptyState, + Loader, + TextArea, + ToggleSwitch, + Tooltip, +} from "components/ui"; +// images +import emptyPage from "public/empty-state/page.svg"; // icons import { ArrowLeftIcon, @@ -40,7 +49,7 @@ import { XMarkIcon, ChevronDownIcon, } from "@heroicons/react/24/outline"; -import { ColorPalletteIcon, ClipboardIcon } from "components/icons"; +import { ColorPalletteIcon } from "components/icons"; // helpers import { render24HourFormatTime, renderShortDate } from "helpers/date-time.helper"; import { copyTextToClipboard, truncateText } from "helpers/string.helper"; @@ -82,7 +91,7 @@ const SinglePage: NextPage = () => { : null ); - const { data: pageDetails } = useSWR( + const { data: pageDetails, error } = useSWR( workspaceSlug && projectId && pageId ? PAGE_DETAILS(pageId as string) : null, workspaceSlug && projectId ? () => @@ -267,13 +276,6 @@ const SinglePage: NextPage = () => { ); }; - const handleNewBlock = useCallback(() => { - setCreateBlockForm(true); - scrollToRef.current?.scrollIntoView({ - behavior: "smooth", - }); - }, [setCreateBlockForm, scrollToRef]); - const handleShowBlockToggle = async () => { if (!workspaceSlug || !projectId) return; @@ -311,22 +313,21 @@ const SinglePage: NextPage = () => { }); }; - const options = - labels?.map((label) => ({ - value: label.id, - query: label.name, - content: ( -
- - {label.name} -
- ), - })) ?? []; + const options = labels?.map((label) => ({ + value: label.id, + query: label.name, + content: ( +
+ + {label.name} +
+ ), + })); useEffect(() => { if (!pageDetails) return; @@ -346,11 +347,21 @@ const SinglePage: NextPage = () => { breadcrumbs={ - + } > - {pageDetails ? ( + {error ? ( + router.push(`/${workspaceSlug}/projects/${projectId}/pages`), + }} + /> + ) : pageDetails ? (
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx index b1cbf97f2..3597f113f 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx @@ -12,11 +12,13 @@ import { IssueViewContextProvider } from "contexts/issue-view.context"; // components import { IssuesFilterView, IssuesView } from "components/core"; // ui -import { CustomMenu, PrimaryButton } from "components/ui"; +import { CustomMenu, EmptyState, PrimaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; import { StackedLayersIcon } from "components/icons"; +// images +import emptyView from "public/empty-state/view.svg"; // helpers import { truncateText } from "helpers/string.helper"; // fetch-keys @@ -40,7 +42,7 @@ const SingleView: React.FC = () => { : null ); - const { data: viewDetails } = useSWR( + const { data: viewDetails, error } = useSWR( workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null, workspaceSlug && projectId && viewId ? () => @@ -101,9 +103,21 @@ const SingleView: React.FC = () => {
} > -
- -
+ {error ? ( + router.push(`/${workspaceSlug}/projects/${projectId}/views`), + }} + /> + ) : ( +
+ +
+ )} ); From 5aad6c71da63a8a5c35bde2aa876c86304759f7b Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:23:55 +0530 Subject: [PATCH 09/82] fix: notification read status being toggled when click on link (#1769) --- .../notifications/notification-card.tsx | 4 +++- .../notifications/notification-popover.tsx | 4 +++- apps/app/hooks/use-user-notifications.tsx | 21 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/app/components/notifications/notification-card.tsx b/apps/app/components/notifications/notification-card.tsx index 71c2d676a..3a0d2f9b8 100644 --- a/apps/app/components/notifications/notification-card.tsx +++ b/apps/app/components/notifications/notification-card.tsx @@ -27,6 +27,7 @@ import { snoozeOptions } from "constants/notification"; type NotificationCardProps = { notification: IUserNotification; markNotificationReadStatus: (notificationId: string) => Promise; + markNotificationReadStatusToggle: (notificationId: string) => Promise; markNotificationArchivedStatus: (notificationId: string) => Promise; setSelectedNotificationForSnooze: (notificationId: string) => void; markSnoozeNotification: (notificationId: string, dateTime?: Date | undefined) => Promise; @@ -36,6 +37,7 @@ export const NotificationCard: React.FC = (props) => { const { notification, markNotificationReadStatus, + markNotificationReadStatusToggle, markNotificationArchivedStatus, setSelectedNotificationForSnooze, markSnoozeNotification, @@ -159,7 +161,7 @@ export const NotificationCard: React.FC = (props) => { name: notification.read_at ? "Mark as unread" : "Mark as read", icon: "chat_bubble", onClick: () => { - markNotificationReadStatus(notification.id).then(() => { + markNotificationReadStatusToggle(notification.id).then(() => { setToastAlert({ title: notification.read_at ? "Notification marked as unread" diff --git a/apps/app/components/notifications/notification-popover.tsx b/apps/app/components/notifications/notification-popover.tsx index 0e57c472c..cd8c50bd2 100644 --- a/apps/app/components/notifications/notification-popover.tsx +++ b/apps/app/components/notifications/notification-popover.tsx @@ -38,6 +38,7 @@ export const NotificationPopover = () => { notificationMutate, markNotificationArchivedStatus, markNotificationReadStatus, + markNotificationAsRead, markSnoozeNotification, notificationCount, totalNotificationCount, @@ -128,7 +129,8 @@ export const NotificationPopover = () => { key={notification.id} notification={notification} markNotificationArchivedStatus={markNotificationArchivedStatus} - markNotificationReadStatus={markNotificationReadStatus} + markNotificationReadStatus={markNotificationAsRead} + markNotificationReadStatusToggle={markNotificationReadStatus} setSelectedNotificationForSnooze={setSelectedNotificationForSnooze} markSnoozeNotification={markSnoozeNotification} /> diff --git a/apps/app/hooks/use-user-notifications.tsx b/apps/app/hooks/use-user-notifications.tsx index 33c2d4e65..868633db0 100644 --- a/apps/app/hooks/use-user-notifications.tsx +++ b/apps/app/hooks/use-user-notifications.tsx @@ -185,6 +185,26 @@ const useUserNotification = () => { } }; + const markNotificationAsRead = async (notificationId: string) => { + if (!workspaceSlug) return; + + const isRead = + notifications?.find((notification) => notification.id === notificationId)?.read_at !== null; + + if (isRead) return; + + mutateNotification(notificationId, { read_at: new Date() }); + handleReadMutation("read"); + + await userNotificationServices + .markUserNotificationAsRead(workspaceSlug.toString(), notificationId) + .catch(() => { + throw new Error("Something went wrong"); + }); + + mutateNotificationCount(); + }; + const markNotificationArchivedStatus = async (notificationId: string) => { if (!workspaceSlug) return; const isArchived = @@ -283,6 +303,7 @@ const useUserNotification = () => { hasMore, isRefreshing, setFetchNotifications, + markNotificationAsRead, }; }; From 97c3fb40e77d2e26caad7d4f4bdaf31713828fd3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:01:31 +0530 Subject: [PATCH 10/82] fix: custom theme persisting after signing out (#1780) * fix: custom theme persistence * chore: remove console logs * fix: build error * fix: change theme from command k --- .../change-interface-theme.tsx | 38 ++- .../core/theme/custom-theme-selector.tsx | 2 + .../components/core/theme/theme-switch.tsx | 221 ++++++++++-------- .../components/workspace/sidebar-dropdown.tsx | 6 +- apps/app/constants/themes.ts | 10 + apps/app/contexts/theme.context.tsx | 23 +- apps/app/helpers/theme.helper.ts | 60 ++--- .../me/profile/preferences.tsx | 11 +- apps/app/pages/_app.tsx | 2 +- apps/app/pages/colors.tsx | 143 ------------ apps/app/pages/index.tsx | 34 ++- apps/app/pages/magic-sign-in.tsx | 13 +- apps/app/pages/onboarding.tsx | 7 +- apps/app/pages/reset-password.tsx | 8 + apps/app/pages/sign-up.tsx | 8 + apps/app/services/authentication.service.ts | 7 +- apps/app/types/users.d.ts | 1 + 17 files changed, 287 insertions(+), 307 deletions(-) delete mode 100644 apps/app/pages/colors.tsx diff --git a/apps/app/components/command-palette/change-interface-theme.tsx b/apps/app/components/command-palette/change-interface-theme.tsx index b2b43c670..b34212b7f 100644 --- a/apps/app/components/command-palette/change-interface-theme.tsx +++ b/apps/app/components/command-palette/change-interface-theme.tsx @@ -5,6 +5,8 @@ import { Command } from "cmdk"; import { THEMES_OBJ } from "constants/themes"; import { useTheme } from "next-themes"; import { SettingIcon } from "components/icons"; +import userService from "services/user.service"; +import useUser from "hooks/use-user"; type Props = { setIsPaletteOpen: Dispatch>; @@ -12,24 +14,50 @@ type Props = { export const ChangeInterfaceTheme: React.FC = ({ setIsPaletteOpen }) => { const [mounted, setMounted] = useState(false); + const { setTheme } = useTheme(); + const { user, mutateUser } = useUser(); + + const updateUserTheme = (newTheme: string) => { + if (!user) return; + + setTheme(newTheme); + + mutateUser((prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + theme: { + ...prevData.theme, + theme: newTheme, + }, + }; + }, false); + + userService.updateUser({ + theme: { + ...user.theme, + theme: newTheme, + }, + }); + }; + // useEffect only runs on the client, so now we can safely show the UI useEffect(() => { setMounted(true); }, []); - if (!mounted) { - return null; - } + if (!mounted) return null; return ( <> - {THEMES_OBJ.map((theme) => ( + {THEMES_OBJ.filter((t) => t.value !== "custom").map((theme) => ( { - setTheme(theme.value); + updateUserTheme(theme.value); setIsPaletteOpen(false); }} className="focus:outline-none" diff --git a/apps/app/components/core/theme/custom-theme-selector.tsx b/apps/app/components/core/theme/custom-theme-selector.tsx index 40450ee2c..668083b59 100644 --- a/apps/app/components/core/theme/custom-theme-selector.tsx +++ b/apps/app/components/core/theme/custom-theme-selector.tsx @@ -28,6 +28,7 @@ const defaultValues: ICustomTheme = { sidebarText: "#c5c5c5", darkPalette: false, palette: "", + theme: "custom", }; export const CustomThemeSelector: React.FC = ({ preLoadedData }) => { @@ -56,6 +57,7 @@ export const CustomThemeSelector: React.FC = ({ preLoadedData }) => { sidebarText: formData.sidebarText, darkPalette: darkPalette, palette: `${formData.background},${formData.text},${formData.primary},${formData.sidebarBackground},${formData.sidebarText}`, + theme: "custom", }; await userService diff --git a/apps/app/components/core/theme/theme-switch.tsx b/apps/app/components/core/theme/theme-switch.tsx index 39b570bc5..8ac950b8c 100644 --- a/apps/app/components/core/theme/theme-switch.tsx +++ b/apps/app/components/core/theme/theme-switch.tsx @@ -1,142 +1,161 @@ -import { useState, useEffect, Dispatch, SetStateAction } from "react"; +import { useState, useEffect } from "react"; +// next-themes import { useTheme } from "next-themes"; - +// services +import userService from "services/user.service"; +// hooks +import useUser from "hooks/use-user"; // constants import { THEMES_OBJ } from "constants/themes"; // ui import { CustomSelect } from "components/ui"; // types -import { ICustomTheme, IUser } from "types"; +import { ICustomTheme } from "types"; +import { unsetCustomCssVariables } from "helpers/theme.helper"; type Props = { - user: IUser | undefined; - setPreLoadedData: Dispatch>; + setPreLoadedData: React.Dispatch>; customThemeSelectorOptions: boolean; - setCustomThemeSelectorOptions: Dispatch>; + setCustomThemeSelectorOptions: React.Dispatch>; }; export const ThemeSwitch: React.FC = ({ - user, setPreLoadedData, customThemeSelectorOptions, setCustomThemeSelectorOptions, }) => { const [mounted, setMounted] = useState(false); + const { theme, setTheme } = useTheme(); + const { user, mutateUser } = useUser(); + + const updateUserTheme = (newTheme: string) => { + if (!user) return; + + setTheme(newTheme); + + mutateUser((prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + theme: { + ...prevData.theme, + theme: newTheme, + }, + }; + }, false); + + userService.updateUser({ + theme: { + ...user.theme, + theme: newTheme, + }, + }); + }; + // useEffect only runs on the client, so now we can safely show the UI useEffect(() => { setMounted(true); }, []); - if (!mounted) { - return null; - } + if (!mounted) return null; const currentThemeObj = THEMES_OBJ.find((t) => t.value === theme); return ( - <> - + +
-
-
-
- {currentThemeObj.label} + /> +
- ) : ( - "Select your theme" - ) - } - onChange={({ value, type }: { value: string; type: string }) => { - if (value === "custom") { - if (user?.theme.palette) { - setPreLoadedData({ - background: user.theme.background !== "" ? user.theme.background : "#0d101b", - text: user.theme.text !== "" ? user.theme.text : "#c5c5c5", - primary: user.theme.primary !== "" ? user.theme.primary : "#3f76ff", - sidebarBackground: - user.theme.sidebarBackground !== "" ? user.theme.sidebarBackground : "#0d101b", - sidebarText: user.theme.sidebarText !== "" ? user.theme.sidebarText : "#c5c5c5", - darkPalette: false, - palette: - user.theme.palette !== ",,,," - ? user.theme.palette - : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", - }); - } - - if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true); - } else { - if (customThemeSelectorOptions) setCustomThemeSelectorOptions(false); - - for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) { - document.documentElement.style.removeProperty(`--color-background-${i}`); - document.documentElement.style.removeProperty(`--color-text-${i}`); - document.documentElement.style.removeProperty(`--color-border-${i}`); - document.documentElement.style.removeProperty(`--color-primary-${i}`); - document.documentElement.style.removeProperty(`--color-sidebar-background-${i}`); - document.documentElement.style.removeProperty(`--color-sidebar-text-${i}`); - document.documentElement.style.removeProperty(`--color-sidebar-border-${i}`); - } + {currentThemeObj.label} +
+ ) : ( + "Select your theme" + ) + } + onChange={({ value, type }: { value: string; type: string }) => { + if (value === "custom") { + if (user?.theme.palette) { + setPreLoadedData({ + background: user.theme.background !== "" ? user.theme.background : "#0d101b", + text: user.theme.text !== "" ? user.theme.text : "#c5c5c5", + primary: user.theme.primary !== "" ? user.theme.primary : "#3f76ff", + sidebarBackground: + user.theme.sidebarBackground !== "" ? user.theme.sidebarBackground : "#0d101b", + sidebarText: user.theme.sidebarText !== "" ? user.theme.sidebarText : "#c5c5c5", + darkPalette: false, + palette: + user.theme.palette !== ",,,," + ? user.theme.palette + : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", + theme: "custom", + }); } - setTheme(value); - document.documentElement.style.setProperty("color-scheme", type); - }} - input - width="w-full" - position="right" - > - {THEMES_OBJ.map(({ value, label, type, icon }) => ( - -
+ if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true); + } else { + if (customThemeSelectorOptions) setCustomThemeSelectorOptions(false); + unsetCustomCssVariables(); + } + + updateUserTheme(value); + document.documentElement.style.setProperty("--color-scheme", type); + }} + input + width="w-full" + position="right" + > + {THEMES_OBJ.map(({ value, label, type, icon }) => ( + +
+
-
-
-
- {label} + /> +
- - ))} - - + {label} +
+ + ))} + ); }; diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 3b9aa97eb..7a43cfb74 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -3,10 +3,10 @@ import { Fragment } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; -// next-themes -import { useTheme } from "next-themes"; // headless ui import { Menu, Transition } from "@headlessui/react"; +// next-themes +import { useTheme } from "next-themes"; // hooks import useUser from "hooks/use-user"; import useThemeHook from "hooks/use-theme"; @@ -91,7 +91,7 @@ export const WorkspaceSidebarDropdown = () => { .then(() => { mutateUser(undefined); router.push("/"); - setTheme("dark"); + setTheme("system"); }) .catch(() => setToastAlert({ diff --git a/apps/app/constants/themes.ts b/apps/app/constants/themes.ts index 78420843a..74b74caba 100644 --- a/apps/app/constants/themes.ts +++ b/apps/app/constants/themes.ts @@ -1,6 +1,16 @@ export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"]; export const THEMES_OBJ = [ + { + value: "system", + label: "System Preference", + type: "light", + icon: { + border: "#DEE2E6", + color1: "#FAFAFA", + color2: "#3F76FF", + }, + }, { value: "light", label: "Light", diff --git a/apps/app/contexts/theme.context.tsx b/apps/app/contexts/theme.context.tsx index 0f5489125..d338b9b6b 100644 --- a/apps/app/contexts/theme.context.tsx +++ b/apps/app/contexts/theme.context.tsx @@ -11,7 +11,7 @@ import projectService from "services/project.service"; // fetch-keys import { USER_PROJECT_VIEW } from "constants/fetch-keys"; // helper -import { applyTheme } from "helpers/theme.helper"; +import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; // constants export const themeContext = createContext({} as ContextType); @@ -92,15 +92,18 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({ useEffect(() => { const theme = localStorage.getItem("theme"); - if (theme && theme === "custom") { - if (user && user.theme.palette) { - applyTheme( - user.theme.palette !== ",,,," - ? user.theme.palette - : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", - user.theme.darkPalette - ); - } + + if (theme) { + if (theme === "custom") { + if (user && user.theme.palette) { + applyTheme( + user.theme.palette !== ",,,," + ? user.theme.palette + : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", + user.theme.darkPalette + ); + } + } else unsetCustomCssVariables(); } }, [user]); diff --git a/apps/app/helpers/theme.helper.ts b/apps/app/helpers/theme.helper.ts index 993a6b9ea..8a521bc31 100644 --- a/apps/app/helpers/theme.helper.ts +++ b/apps/app/helpers/theme.helper.ts @@ -60,6 +60,7 @@ const calculateShades = (hexValue: string): TShades => { }; export const applyTheme = (palette: string, isDarkPalette: boolean) => { + const dom = document?.querySelector("[data-theme='custom']"); // palette: [bg, text, primary, sidebarBg, sidebarText] const values: string[] = palette.split(","); values.push(isDarkPalette ? "dark" : "light"); @@ -79,41 +80,40 @@ export const applyTheme = (palette: string, isDarkPalette: boolean) => { const sidebarBackgroundRgbValues = `${sidebarBackgroundShades[shade].r}, ${sidebarBackgroundShades[shade].g}, ${sidebarBackgroundShades[shade].b}`; const sidebarTextRgbValues = `${sidebarTextShades[shade].r}, ${sidebarTextShades[shade].g}, ${sidebarTextShades[shade].b}`; - document - .querySelector("[data-theme='custom']") - ?.style.setProperty(`--color-background-${shade}`, bgRgbValues); - document - .querySelector("[data-theme='custom']") - ?.style.setProperty(`--color-text-${shade}`, textRgbValues); - document - .querySelector("[data-theme='custom']") - ?.style.setProperty(`--color-primary-${shade}`, primaryRgbValues); - document - .querySelector("[data-theme='custom']") - ?.style.setProperty(`--color-sidebar-background-${shade}`, sidebarBackgroundRgbValues); - document - .querySelector("[data-theme='custom']") - ?.style.setProperty(`--color-sidebar-text-${shade}`, sidebarTextRgbValues); + dom?.style.setProperty(`--color-background-${shade}`, bgRgbValues); + dom?.style.setProperty(`--color-text-${shade}`, textRgbValues); + dom?.style.setProperty(`--color-primary-${shade}`, primaryRgbValues); + dom?.style.setProperty(`--color-sidebar-background-${shade}`, sidebarBackgroundRgbValues); + dom?.style.setProperty(`--color-sidebar-text-${shade}`, sidebarTextRgbValues); if (i >= 100 && i <= 400) { const borderShade = i === 100 ? 70 : i === 200 ? 80 : i === 300 ? 90 : 100; - document - .querySelector("[data-theme='custom']") - ?.style.setProperty( - `--color-border-${shade}`, - `${bgShades[borderShade].r}, ${bgShades[borderShade].g}, ${bgShades[borderShade].b}` - ); - document - .querySelector("[data-theme='custom']") - ?.style.setProperty( - `--color-sidebar-border-${shade}`, - `${sidebarBackgroundShades[borderShade].r}, ${sidebarBackgroundShades[borderShade].g}, ${sidebarBackgroundShades[borderShade].b}` - ); + dom?.style.setProperty( + `--color-border-${shade}`, + `${bgShades[borderShade].r}, ${bgShades[borderShade].g}, ${bgShades[borderShade].b}` + ); + dom?.style.setProperty( + `--color-sidebar-border-${shade}`, + `${sidebarBackgroundShades[borderShade].r}, ${sidebarBackgroundShades[borderShade].g}, ${sidebarBackgroundShades[borderShade].b}` + ); } } - document - .querySelector("[data-theme='custom']") - ?.style.setProperty("--color-scheme", values[5]); + dom?.style.setProperty("--color-scheme", values[5]); +}; + +export const unsetCustomCssVariables = () => { + for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) { + const dom = document.querySelector("[data-theme='custom']"); + + dom?.style.removeProperty(`--color-background-${i}`); + dom?.style.removeProperty(`--color-text-${i}`); + dom?.style.removeProperty(`--color-border-${i}`); + dom?.style.removeProperty(`--color-primary-${i}`); + dom?.style.removeProperty(`--color-sidebar-background-${i}`); + dom?.style.removeProperty(`--color-sidebar-text-${i}`); + dom?.style.removeProperty(`--color-sidebar-border-${i}`); + dom?.style.removeProperty("--color-scheme"); + } }; diff --git a/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx b/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx index 605131aa4..61e743fc3 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; -import { useTheme } from "next-themes"; +// next-themes +import { useTheme } from "next-themes"; // hooks import useUserAuth from "hooks/use-user-auth"; // layouts @@ -15,11 +16,13 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { ICustomTheme } from "types"; const ProfilePreferences = () => { - const { user: myProfile } = useUserAuth(); - const { theme } = useTheme(); const [customThemeSelectorOptions, setCustomThemeSelectorOptions] = useState(false); const [preLoadedData, setPreLoadedData] = useState(null); + const { theme } = useTheme(); + + const { user: myProfile } = useUserAuth(); + useEffect(() => { if (theme === "custom") { if (myProfile?.theme.palette) @@ -37,6 +40,7 @@ const ProfilePreferences = () => { myProfile.theme.palette !== ",,,," ? myProfile.theme.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", + theme: "custom", }); if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true); } @@ -71,7 +75,6 @@ const ProfilePreferences = () => {
- + diff --git a/apps/app/pages/colors.tsx b/apps/app/pages/colors.tsx deleted file mode 100644 index d52e2cf41..000000000 --- a/apps/app/pages/colors.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from "react"; - -// layouts -import DefaultLayout from "layouts/default-layout"; -import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; -// types -import type { NextPage } from "next"; - -const Colors: NextPage = () => ( - - -
-
- Primary: -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Background: -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Text: -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sidebar Background: -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sidebar Text: -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -); - -export default Colors; diff --git a/apps/app/pages/index.tsx b/apps/app/pages/index.tsx index 6cef211c0..f9e4d9844 100644 --- a/apps/app/pages/index.tsx +++ b/apps/app/pages/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import Image from "next/image"; @@ -22,6 +22,8 @@ import { import { Spinner } from "components/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; +import { useTheme } from "next-themes"; +import { ICurrentUserResponse, IUser } from "types"; // types type EmailPasswordFormValues = { email: string; @@ -34,6 +36,12 @@ const HomePage: NextPage = () => { const { setToastAlert } = useToast(); + const { setTheme } = useTheme(); + + const changeTheme = (user: IUser) => { + setTheme(user.theme.theme ?? "system"); + }; + const handleGoogleSignIn = async ({ clientId, credential }: any) => { try { if (clientId && credential) { @@ -43,7 +51,10 @@ const HomePage: NextPage = () => { clientId, }; const response = await authenticationService.socialAuth(socialAuthPayload); - if (response && response?.user) mutateUser(); + if (response && response?.user) { + mutateUser(); + changeTheme(response.user); + } } else { throw Error("Cant find credentials"); } @@ -66,7 +77,10 @@ const HomePage: NextPage = () => { clientId: process.env.NEXT_PUBLIC_GITHUB_ID, }; const response = await authenticationService.socialAuth(socialAuthPayload); - if (response && response?.user) mutateUser(); + if (response && response?.user) { + mutateUser(); + changeTheme(response.user); + } } else { throw Error("Cant find credentials"); } @@ -85,7 +99,10 @@ const HomePage: NextPage = () => { .emailLogin(formData) .then((response) => { try { - if (response) mutateUser(); + if (response) { + mutateUser(); + changeTheme(response.user); + } } catch (err: any) { setToastAlert({ type: "error", @@ -109,7 +126,10 @@ const HomePage: NextPage = () => { const handleEmailCodeSignIn = async (response: any) => { try { - if (response) mutateUser(); + if (response) { + mutateUser(); + changeTheme(response.user); + } } catch (err: any) { setToastAlert({ type: "error", @@ -120,6 +140,10 @@ const HomePage: NextPage = () => { } }; + useEffect(() => { + setTheme("system"); + }, [setTheme]); + return ( {isLoading ? ( diff --git a/apps/app/pages/magic-sign-in.tsx b/apps/app/pages/magic-sign-in.tsx index c959764a2..7de298d39 100644 --- a/apps/app/pages/magic-sign-in.tsx +++ b/apps/app/pages/magic-sign-in.tsx @@ -1,6 +1,9 @@ import React, { useState, useEffect } from "react"; -// next imports + import { useRouter } from "next/router"; + +// next-themes +import { useTheme } from "next-themes"; // layouts import DefaultLayout from "layouts/default-layout"; // services @@ -17,11 +20,17 @@ const MagicSignIn: NextPage = () => { const { setToastAlert } = useToast(); - const { user, isLoading, mutateUser } = useUserAuth("sign-in"); + const { setTheme } = useTheme(); + + const { mutateUser } = useUserAuth("sign-in"); const [isSigningIn, setIsSigningIn] = useState(false); const [errorSigningIn, setErrorSignIn] = useState(); + useEffect(() => { + setTheme("system"); + }, [setTheme]); + useEffect(() => { setIsSigningIn(() => false); setErrorSignIn(() => undefined); diff --git a/apps/app/pages/onboarding.tsx b/apps/app/pages/onboarding.tsx index 217584dd0..51148b19e 100644 --- a/apps/app/pages/onboarding.tsx +++ b/apps/app/pages/onboarding.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; -import Router from "next/router"; import Image from "next/image"; import useSWR, { mutate } from "swr"; @@ -32,7 +31,7 @@ import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; const Onboarding: NextPage = () => { const [step, setStep] = useState(null); - const { theme } = useTheme(); + const { theme, setTheme } = useTheme(); const { user, isLoading: userLoading } = useUserAuth("onboarding"); @@ -117,6 +116,10 @@ const Onboarding: NextPage = () => { await userService.updateUserOnBoard({ userRole: user.role }, user); }; + useEffect(() => { + setTheme("system"); + }, [setTheme]); + useEffect(() => { const handleStepChange = async () => { if (!user || !invitations) return; diff --git a/apps/app/pages/reset-password.tsx b/apps/app/pages/reset-password.tsx index c25974cb9..fa17d2333 100644 --- a/apps/app/pages/reset-password.tsx +++ b/apps/app/pages/reset-password.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Image from "next/image"; +// next-themes +import { useTheme } from "next-themes"; // react-hook-form import { useForm } from "react-hook-form"; // hooks @@ -31,6 +33,8 @@ const ResetPasswordPage: NextPage = () => { const { setToastAlert } = useToast(); + const { setTheme } = useTheme(); + const { register, handleSubmit, @@ -76,6 +80,10 @@ const ResetPasswordPage: NextPage = () => { ); }; + useEffect(() => { + setTheme("system"); + }, [setTheme]); + useEffect(() => { if (parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0")) router.push("/"); else setIsLoading(false); diff --git a/apps/app/pages/sign-up.tsx b/apps/app/pages/sign-up.tsx index fe8960d73..72a391ea4 100644 --- a/apps/app/pages/sign-up.tsx +++ b/apps/app/pages/sign-up.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; +// next-themes +import { useTheme } from "next-themes"; // services import authenticationService from "services/authentication.service"; // hooks @@ -31,6 +33,8 @@ const SignUp: NextPage = () => { const { setToastAlert } = useToast(); + const { setTheme } = useTheme(); + const { mutateUser } = useUserAuth("sign-in"); const handleSignUp = async (formData: EmailPasswordFormValues) => { @@ -62,6 +66,10 @@ const SignUp: NextPage = () => { ); }; + useEffect(() => { + setTheme("system"); + }, [setTheme]); + useEffect(() => { if (parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0")) router.push("/"); else setIsLoading(false); diff --git a/apps/app/services/authentication.service.ts b/apps/app/services/authentication.service.ts index 86f55e329..e4a33bff8 100644 --- a/apps/app/services/authentication.service.ts +++ b/apps/app/services/authentication.service.ts @@ -1,5 +1,6 @@ // services import APIService from "services/api.service"; +import { ICurrentUserResponse } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; @@ -32,7 +33,11 @@ class AuthService extends APIService { }); } - async socialAuth(data: any) { + async socialAuth(data: any): Promise<{ + access_token: string; + refresh_toke: string; + user: ICurrentUserResponse; + }> { return this.post("/api/social-auth/", data, { headers: {} }) .then((response) => { this.setAccessToken(response?.data?.access_token); diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index 3151345e0..ec77df242 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -46,6 +46,7 @@ export interface ICustomTheme { sidebarText: string; darkPalette: boolean; palette: string; + theme: string; } export interface ICurrentUserResponse extends IUser { From 2b46e5f97770c71029081995129af23dbb6648f1 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Mon, 7 Aug 2023 02:29:04 -0400 Subject: [PATCH 11/82] feat: issue export csv (#1781) * feat: created issue export csv * fix: optimized the queries --------- Co-authored-by: NarayanBavisetti --- apiserver/plane/api/urls.py | 6 + apiserver/plane/api/views/__init__.py | 1 + apiserver/plane/api/views/issue.py | 28 +++ .../plane/bgtasks/project_issue_export.py | 191 ++++++++++++++++++ .../templates/emails/exports/issues.html | 9 + 5 files changed, 235 insertions(+) create mode 100644 apiserver/plane/bgtasks/project_issue_export.py create mode 100644 apiserver/templates/emails/exports/issues.html diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index c8b5e7b5e..2d1b2c908 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -86,6 +86,7 @@ from plane.api.views import ( IssueSubscriberViewSet, IssueReactionViewSet, CommentReactionViewSet, + ExportIssuesEndpoint, ## End Issues # States StateViewSet, @@ -808,6 +809,11 @@ urlpatterns = [ IssueAttachmentEndpoint.as_view(), name="project-issue-attachments", ), + path( + "workspaces//export-issues/", + ExportIssuesEndpoint.as_view(), + name="export-issues", + ), ## End Issues ## Issue Activity path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 75509a16c..92b647a97 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -75,6 +75,7 @@ from .issue import ( IssueSubscriberViewSet, CommentReactionViewSet, IssueReactionViewSet, + ExportIssuesEndpoint ) from .auth_extended import ( diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 9369ccf2b..95e598dae 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -74,6 +74,7 @@ from plane.db.models import ( from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters +from plane.bgtasks.project_issue_export import issue_export_task class IssueViewSet(BaseViewSet): @@ -1445,3 +1446,30 @@ class CommentReactionViewSet(BaseViewSet): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + + +class ExportIssuesEndpoint(BaseAPIView): + permission_classes = [ + WorkSpaceAdminPermission, + ] + + def post(self, request, slug): + try: + + issue_export_task.delay( + email=request.user.email, data=request.data, slug=slug ,exporter_name=request.user.first_name + ) + + return Response( + { + "message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}" + }, + status=status.HTTP_200_OK, + ) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) \ No newline at end of file diff --git a/apiserver/plane/bgtasks/project_issue_export.py b/apiserver/plane/bgtasks/project_issue_export.py new file mode 100644 index 000000000..75088be9d --- /dev/null +++ b/apiserver/plane/bgtasks/project_issue_export.py @@ -0,0 +1,191 @@ +# Python imports +import csv +import io + +# Django imports +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from django.utils.html import strip_tags +from django.conf import settings +from django.utils import timezone + +# Third party imports +from celery import shared_task +from sentry_sdk import capture_exception + +# Module imports +from plane.db.models import Issue + +@shared_task +def issue_export_task(email, data, slug, exporter_name): + try: + + project_ids = data.get("project_id", []) + issues_filter = {"workspace__slug": slug} + + if project_ids: + issues_filter["project_id__in"] = project_ids + + issues = ( + Issue.objects.filter(**issues_filter) + .select_related("project", "workspace", "state", "parent", "created_by") + .prefetch_related( + "assignees", "labels", "issue_cycle__cycle", "issue_module__module" + ) + .values_list( + "project__identifier", + "sequence_id", + "name", + "description_stripped", + "priority", + "start_date", + "target_date", + "state__name", + "project__name", + "created_at", + "updated_at", + "completed_at", + "archived_at", + "issue_cycle__cycle__name", + "issue_cycle__cycle__start_date", + "issue_cycle__cycle__end_date", + "issue_module__module__name", + "issue_module__module__start_date", + "issue_module__module__target_date", + "created_by__first_name", + "created_by__last_name", + "assignees__first_name", + "assignees__last_name", + "labels__name", + ) + ) + + # CSV header + header = [ + "Issue ID", + "Project", + "Name", + "Description", + "State", + "Priority", + "Created By", + "Assignee", + "Labels", + "Cycle Name", + "Cycle Start Date", + "Cycle End Date", + "Module Name", + "Module Start Date", + "Module Target Date", + "Created At" + "Updated At" + "Completed At" + "Archived At" + ] + + # Prepare the CSV data + rows = [header] + + # Write data for each issue + for issue in issues: + ( + project_identifier, + sequence_id, + name, + description, + priority, + start_date, + target_date, + state_name, + project_name, + created_at, + updated_at, + completed_at, + archived_at, + cycle_name, + cycle_start_date, + cycle_end_date, + module_name, + module_start_date, + module_target_date, + created_by_first_name, + created_by_last_name, + assignees_first_names, + assignees_last_names, + labels_names, + ) = issue + + created_by_fullname = ( + f"{created_by_first_name} {created_by_last_name}" + if created_by_first_name and created_by_last_name + else "" + ) + + assignees_names = "" + if assignees_first_names and assignees_last_names: + assignees_names = ", ".join( + [ + f"{assignees_first_name} {assignees_last_name}" + for assignees_first_name, assignees_last_name in zip( + assignees_first_names, assignees_last_names + ) + ] + ) + + labels_names = ", ".join(labels_names) if labels_names else "" + + row = [ + f"{project_identifier}-{sequence_id}", + project_name, + name, + description, + state_name, + priority, + created_by_fullname, + assignees_names, + labels_names, + cycle_name, + cycle_start_date, + cycle_end_date, + module_name, + module_start_date, + module_target_date, + start_date, + target_date, + created_at, + updated_at, + completed_at, + archived_at, + ] + rows.append(row) + + # Create CSV file in-memory + csv_buffer = io.StringIO() + writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL) + + # Write CSV data to the buffer + for row in rows: + writer.writerow(row) + + subject = "Your Issue Export is ready" + + context = { + "username": exporter_name, + } + + html_content = render_to_string("emails/exports/issues.html", context) + text_content = strip_tags(html_content) + + csv_buffer.seek(0) + msg = EmailMultiAlternatives( + subject, text_content, settings.EMAIL_FROM, [email] + ) + msg.attach(f"{slug}-issues-{timezone.now().date()}.csv", csv_buffer.read(), "text/csv") + msg.send(fail_silently=False) + + except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) + capture_exception(e) + return diff --git a/apiserver/templates/emails/exports/issues.html b/apiserver/templates/emails/exports/issues.html new file mode 100644 index 000000000..a97432b9b --- /dev/null +++ b/apiserver/templates/emails/exports/issues.html @@ -0,0 +1,9 @@ + + + Dear {{username}},
+ Your requested Issue's data has been successfully exported from Plane. The export includes all relevant information about issues you requested from your selected projects.
+ Please find the attachment and download the CSV file. If you have any questions or need further assistance, please don't hesitate to contact our support team at engineering@plane.so. We're here to help!
+ Thank you for using Plane. We hope this export will aid you in effectively managing your projects.
+ Regards, + Team Plane + From a164dfd532dbe4eb13e49d6a994f4f98dcab0fb2 Mon Sep 17 00:00:00 2001 From: DevMiner Date: Mon, 7 Aug 2023 12:22:08 +0200 Subject: [PATCH 12/82] chore(frontend): add `sharp` (#1451) --- apps/app/package.json | 1 + yarn.lock | 263 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 258 insertions(+), 6 deletions(-) diff --git a/apps/app/package.json b/apps/app/package.json index 4397696ab..abaa30754 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -49,6 +49,7 @@ "react-hook-form": "^7.38.0", "react-markdown": "^8.0.7", "remirror": "^2.0.23", + "sharp": "^0.32.1", "swr": "^2.1.3", "tlds": "^1.238.0", "uuid": "^9.0.0" diff --git a/yarn.lock b/yarn.lock index 4a8d0a942..a81c5c77d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3898,6 +3898,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3908,6 +3913,15 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3945,6 +3959,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -4073,6 +4095,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" @@ -4127,16 +4154,32 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + color2k@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/color2k/-/color2k-2.0.2.tgz#ac2b4aea11c822a6bcb70c768b5a289f4fffcebb" integrity sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w== +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -4400,6 +4443,13 @@ decode-named-character-reference@^1.0.0: dependencies: character-entities "^2.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-equal@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -4436,6 +4486,11 @@ deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.9" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4487,6 +4542,11 @@ dequal@^2.0.0: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +detect-libc@^2.0.0, detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" @@ -4621,6 +4681,13 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -5085,6 +5152,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -5225,6 +5297,11 @@ fraction.js@^4.2.0: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -5307,6 +5384,11 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -5539,6 +5621,11 @@ idb@^7.0.1: resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -5575,11 +5662,16 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" @@ -5641,6 +5733,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -6533,6 +6630,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -6554,11 +6656,16 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -6619,6 +6726,11 @@ nanoid@^3.3.4, nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -6680,6 +6792,18 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-abi@^3.3.0: + version "3.45.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" + integrity sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ== + dependencies: + semver "^7.3.5" + +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + node-fetch@^2.6.7: version "2.6.9" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" @@ -6804,7 +6928,7 @@ object.values@^1.1.5, object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -7078,6 +7202,24 @@ postcss@^8.4.14, postcss@^8.4.23: picocolors "^1.0.0" source-map-js "^1.0.2" +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + precision@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/precision/-/precision-1.0.1.tgz#5cf489b9d4a114700d300479cb8b9d85a502cfe8" @@ -7292,6 +7434,14 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" @@ -7319,6 +7469,16 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-beautiful-dnd@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" @@ -7522,6 +7682,15 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -7815,7 +7984,7 @@ safari-14-idb-fix@^1.0.6: resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.6.tgz#cbaabc33a4500c44b5c432d6c525b0ed9b68bb65" integrity sha512-oTEQOdMwRX+uCtWCKT1nx2gAeSdpr8elg/2gcaKUH00SJU2xWESfkx11nmXwTRHy7xfQoj1o4TTQvdmuBosTnA== -safe-buffer@^5.1.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7866,6 +8035,13 @@ semver@^7.2.1, semver@^7.3.7: dependencies: lru-cache "^6.0.0" +semver@^7.3.5, semver@^7.5.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== + dependencies: + lru-cache "^6.0.0" + sentence-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" @@ -7889,6 +8065,20 @@ serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" +sharp@^0.32.1: + version "0.32.1" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.1.tgz#41aa0d0b2048b2e0ee453d9fcb14ec1f408390fe" + integrity sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg== + dependencies: + color "^4.2.3" + detect-libc "^2.0.1" + node-addon-api "^6.1.0" + prebuild-install "^7.1.1" + semver "^7.5.0" + simple-get "^4.0.1" + tar-fs "^2.1.1" + tunnel-agent "^0.6.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7910,6 +8100,27 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0, simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -8051,6 +8262,13 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" @@ -8082,6 +8300,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + style-to-object@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.1.tgz#53cf856f7cf7f172d72939d9679556469ba5de37" @@ -8202,6 +8425,27 @@ tailwindcss@^3.1.6: resolve "^1.22.2" sucrase "^3.32.0" +tar-fs@^2.0.0, tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -8348,6 +8592,13 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + turbo-darwin-64@1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.9.3.tgz#29470b902a1418dae8a88b2620caf917b27480bc" @@ -8630,7 +8881,7 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== -util-deprecate@^1.0.2: +util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== From b6744dcd29d3484a3162729aeb72d66083adafe1 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 8 Aug 2023 12:50:27 +0530 Subject: [PATCH 13/82] Chore: mobx setup and app sidebar and theme management (#1798) * dev: Mobx integration for app sidebar and custom theme * dev: Handled edge case and conditional rendering for mobx store --- .../command-palette/command-pallette.tsx | 9 +- .../core/theme/custom-theme-selector.tsx | 22 ++-- .../notifications/notification-popover.tsx | 10 +- apps/app/components/project/sidebar-list.tsx | 18 +-- .../app/components/workspace/help-section.tsx | 22 ++-- .../components/workspace/sidebar-dropdown.tsx | 10 +- .../app/components/workspace/sidebar-menu.tsx | 10 +- apps/app/layouts/app-layout/app-sidebar.tsx | 11 +- apps/app/lib/mobx/store-init.tsx | 33 ++++++ apps/app/lib/mobx/store-provider.tsx | 30 +++++ apps/app/package.json | 2 + apps/app/pages/_app.tsx | 17 ++- apps/app/store/root.ts | 17 +++ apps/app/store/theme.ts | 66 +++++++++++ apps/app/store/user.ts | 106 ++++++++++++++++++ apps/app/types/users.d.ts | 41 +++++-- yarn.lock | 12 ++ 17 files changed, 377 insertions(+), 59 deletions(-) create mode 100644 apps/app/lib/mobx/store-init.tsx create mode 100644 apps/app/lib/mobx/store-provider.tsx create mode 100644 apps/app/store/root.ts create mode 100644 apps/app/store/theme.ts create mode 100644 apps/app/store/user.ts diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index 0b4c9577b..e96acbebc 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -24,8 +24,12 @@ import issuesService from "services/issues.service"; import inboxService from "services/inbox.service"; // fetch keys import { INBOX_LIST, ISSUE_DETAILS } from "constants/fetch-keys"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; export const CommandPalette: React.FC = () => { + const store: any = useMobxStore(); + const [isPaletteOpen, setIsPaletteOpen] = useState(false); const [isIssueModalOpen, setIsIssueModalOpen] = useState(false); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); @@ -96,7 +100,8 @@ export const CommandPalette: React.FC = () => { setIsIssueModalOpen(true); } else if ((ctrlKey || metaKey) && keyPressed === "b") { e.preventDefault(); - toggleCollapsed(); + // toggleCollapsed(); + store.theme.setSidebarCollapsed(!store?.theme?.sidebarCollapsed); } else if (key === "Delete") { e.preventDefault(); setIsBulkDeleteIssuesModalOpen(true); @@ -120,7 +125,7 @@ export const CommandPalette: React.FC = () => { } } }, - [toggleCollapsed, copyIssueUrlToClipboard] + [copyIssueUrlToClipboard] ); useEffect(() => { diff --git a/apps/app/components/core/theme/custom-theme-selector.tsx b/apps/app/components/core/theme/custom-theme-selector.tsx index 668083b59..9db7b1dd8 100644 --- a/apps/app/components/core/theme/custom-theme-selector.tsx +++ b/apps/app/components/core/theme/custom-theme-selector.tsx @@ -15,6 +15,8 @@ import userService from "services/user.service"; import { applyTheme } from "helpers/theme.helper"; // types import { ICustomTheme } from "types"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; type Props = { preLoadedData?: Partial | null; @@ -32,6 +34,8 @@ const defaultValues: ICustomTheme = { }; export const CustomThemeSelector: React.FC = ({ preLoadedData }) => { + const store: any = useMobxStore(); + const [darkPalette, setDarkPalette] = useState(false); const { @@ -60,21 +64,15 @@ export const CustomThemeSelector: React.FC = ({ preLoadedData }) => { theme: "custom", }; - await userService - .updateUser({ - theme: payload, - }) - .then((res) => { - mutateUser((prevData) => { - if (!prevData) return prevData; - - return { ...prevData, ...res }; - }, false); - + store.user + .updateCurrentUserSettings({ theme: payload }) + .then((response: any) => { setTheme("custom"); applyTheme(payload.palette, darkPalette); }) - .catch((err) => console.log(err)); + .catch((error: any) => { + console.log("error", error); + }); }; const handleUpdateTheme = async (formData: any) => { diff --git a/apps/app/components/notifications/notification-popover.tsx b/apps/app/components/notifications/notification-popover.tsx index cd8c50bd2..da9fb5668 100644 --- a/apps/app/components/notifications/notification-popover.tsx +++ b/apps/app/components/notifications/notification-popover.tsx @@ -21,8 +21,12 @@ import { NotificationsOutlined } from "@mui/icons-material"; import emptyNotification from "public/empty-state/notification.svg"; // helpers import { getNumberCount } from "helpers/string.helper"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; export const NotificationPopover = () => { + const store: any = useMobxStore(); + const { notifications, archived, @@ -77,17 +81,17 @@ export const NotificationPopover = () => { tooltipContent="Notifications" position="right" className="ml-2" - disabled={!sidebarCollapse} + disabled={!store?.theme?.sidebarCollapsed} > - {sidebarCollapse ? null : Notifications} + {store?.theme?.sidebarCollapsed ? null : Notifications} {totalNotificationCount && totalNotificationCount > 0 ? ( {getNumberCount(totalNotificationCount)} diff --git a/apps/app/components/project/sidebar-list.tsx b/apps/app/components/project/sidebar-list.tsx index 1bb3711fc..a753b98ba 100644 --- a/apps/app/components/project/sidebar-list.tsx +++ b/apps/app/components/project/sidebar-list.tsx @@ -26,8 +26,12 @@ import { orderArrayBy } from "helpers/array.helper"; import { IProject } from "types"; // fetch-keys import { PROJECTS_LIST } from "constants/fetch-keys"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; export const ProjectSidebarList: FC = () => { + const store: any = useMobxStore(); + const [deleteProjectModal, setDeleteProjectModal] = useState(false); const [projectToDelete, setProjectToDelete] = useState(null); @@ -139,7 +143,7 @@ export const ProjectSidebarList: FC = () => { {({ open }) => ( <> - {!sidebarCollapse && ( + {!store?.theme?.sidebarCollapsed && ( { handleDeleteProject(project)} @@ -194,7 +198,7 @@ export const ProjectSidebarList: FC = () => { {({ open }) => ( <> - {!sidebarCollapse && ( + {!store?.theme?.sidebarCollapsed && ( { handleDeleteProject(project)} @@ -243,7 +247,7 @@ export const ProjectSidebarList: FC = () => { > {({ open }) => ( <> - {!sidebarCollapse && ( + {!store?.theme?.sidebarCollapsed && ( { handleDeleteProject(project)} handleCopyText={() => handleCopyText(project.id)} shortContextMenu @@ -284,7 +288,7 @@ export const ProjectSidebarList: FC = () => { }} > - {!sidebarCollapse && "Add Project"} + {!store?.theme?.sidebarCollapsed && "Add Project"} )}
diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index d1ea4f8a3..10548a248 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -11,6 +11,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector"; import { Bolt, HelpOutlineOutlined, WestOutlined } from "@mui/icons-material"; import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"; import { DocumentIcon, DiscordIcon, GithubIcon } from "components/icons"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; const helpOptions = [ { @@ -41,6 +43,8 @@ export interface WorkspaceHelpSectionProps { } export const WorkspaceHelpSection: React.FC = ({ setSidebarActive }) => { + const store: any = useMobxStore(); + const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false); const helpOptionsRef = useRef(null); @@ -53,23 +57,23 @@ export const WorkspaceHelpSection: React.FC = ({ setS <>
- {!sidebarCollapse && ( + {!store?.theme?.sidebarCollapsed && (
Free Plan
)}
@@ -122,7 +126,7 @@ export const WorkspaceHelpSection: React.FC = ({ setS >
diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 7a43cfb74..50328ecc3 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -23,6 +23,8 @@ import { CheckIcon, PlusIcon } from "@heroicons/react/24/outline"; import { truncateText } from "helpers/string.helper"; // types import { IWorkspace } from "types"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // Static Data const userLinks = (workspaceSlug: string, userId: string) => [ @@ -54,6 +56,8 @@ const profileLinks = (workspaceSlug: string, userId: string) => [ ]; export const WorkspaceSidebarDropdown = () => { + const store: any = useMobxStore(); + const router = useRouter(); const { workspaceSlug } = router.query; @@ -108,7 +112,7 @@ export const WorkspaceSidebarDropdown = () => {
@@ -123,7 +127,7 @@ export const WorkspaceSidebarDropdown = () => { )}
- {!sidebarCollapse && ( + {!store?.theme?.sidebarCollapsed && (

{activeWorkspace?.name ? truncateText(activeWorkspace.name, 14) : "Loading..."}

@@ -243,7 +247,7 @@ export const WorkspaceSidebarDropdown = () => { - {!sidebarCollapse && ( + {!store?.theme?.sidebarCollapsed && ( diff --git a/apps/app/components/workspace/sidebar-menu.tsx b/apps/app/components/workspace/sidebar-menu.tsx index 6a2d94cb1..3e4d5472b 100644 --- a/apps/app/components/workspace/sidebar-menu.tsx +++ b/apps/app/components/workspace/sidebar-menu.tsx @@ -16,6 +16,8 @@ import { TaskAltOutlined, WorkOutlineOutlined, } from "@mui/icons-material"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; const workspaceLinks = (workspaceSlug: string) => [ { @@ -41,6 +43,8 @@ const workspaceLinks = (workspaceSlug: string) => [ ]; export const WorkspaceSidebarMenu = () => { + const store: any = useMobxStore(); + const router = useRouter(); const { workspaceSlug } = router.query; @@ -61,17 +65,17 @@ export const WorkspaceSidebarMenu = () => { tooltipContent={link.name} position="right" className="ml-2" - disabled={!sidebarCollapse} + disabled={!store?.theme?.sidebarCollapsed} >
{} - {!sidebarCollapse && link.name} + {!store?.theme?.sidebarCollapsed && link.name}
diff --git a/apps/app/layouts/app-layout/app-sidebar.tsx b/apps/app/layouts/app-layout/app-sidebar.tsx index 8ea9e00a1..60ef645cf 100644 --- a/apps/app/layouts/app-layout/app-sidebar.tsx +++ b/apps/app/layouts/app-layout/app-sidebar.tsx @@ -7,20 +7,25 @@ import { WorkspaceSidebarMenu, } from "components/workspace"; import { ProjectSidebarList } from "components/project"; +// mobx react lite +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; export interface SidebarProps { toggleSidebar: boolean; setToggleSidebar: React.Dispatch>; } -const Sidebar: React.FC = ({ toggleSidebar, setToggleSidebar }) => { +const Sidebar: React.FC = observer(({ toggleSidebar, setToggleSidebar }) => { + const store: any = useMobxStore(); // theme const { collapsed: sidebarCollapse } = useTheme(); return (
@@ -31,6 +36,6 @@ const Sidebar: React.FC = ({ toggleSidebar, setToggleSidebar }) =>
); -}; +}); export default Sidebar; diff --git a/apps/app/lib/mobx/store-init.tsx b/apps/app/lib/mobx/store-init.tsx new file mode 100644 index 000000000..9eb0d7493 --- /dev/null +++ b/apps/app/lib/mobx/store-init.tsx @@ -0,0 +1,33 @@ +import { useEffect } from "react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; + +const MobxStoreInit = () => { + const store: any = useMobxStore(); + + useEffect(() => { + // sidebar collapsed toggle + if ( + localStorage && + localStorage.getItem("app_sidebar_collapsed") && + store?.theme?.sidebarCollapsed === null + ) + store.theme.setSidebarCollapsed( + localStorage.getItem("app_sidebar_collapsed") + ? localStorage.getItem("app_sidebar_collapsed") === "true" + ? true + : false + : false + ); + + // theme + if (localStorage && localStorage.getItem("theme") && store.theme.theme === null) + store.theme.setTheme( + localStorage.getItem("theme") ? localStorage.getItem("theme") : "system" + ); + }, [store?.theme]); + + return <>; +}; + +export default MobxStoreInit; diff --git a/apps/app/lib/mobx/store-provider.tsx b/apps/app/lib/mobx/store-provider.tsx new file mode 100644 index 000000000..244968028 --- /dev/null +++ b/apps/app/lib/mobx/store-provider.tsx @@ -0,0 +1,30 @@ +import { createContext, useContext } from "react"; +// mobx store +import { RootStore } from "store/root"; + +let rootStore: any = null; + +export const MobxStoreContext = createContext(null); + +const initializeStore = () => { + const _rootStore = rootStore ?? new RootStore(); + + if (typeof window === "undefined") return _rootStore; + + if (!rootStore) rootStore = _rootStore; + + return _rootStore; +}; + +export const MobxStoreProvider = ({ children }: any) => { + const store = initializeStore(); + + return {children}; +}; + +// hook +export const useMobxStore = () => { + const context = useContext(MobxStoreContext); + if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider"); + return context; +}; diff --git a/apps/app/package.json b/apps/app/package.json index abaa30754..6b1d58692 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -36,6 +36,8 @@ "dotenv": "^16.0.3", "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", + "mobx": "^6.10.0", + "mobx-react-lite": "^4.0.3", "next": "12.3.2", "next-pwa": "^5.6.0", "next-themes": "^0.2.1", diff --git a/apps/app/pages/_app.tsx b/apps/app/pages/_app.tsx index 6681a7fb9..5ef047ca9 100644 --- a/apps/app/pages/_app.tsx +++ b/apps/app/pages/_app.tsx @@ -33,6 +33,9 @@ import { SITE_KEYWORDS, SITE_TITLE, } from "constants/seo-variables"; +// mobx store provider +import { MobxStoreProvider } from "lib/mobx/store-provider"; +import MobxStoreInit from "lib/mobx/store-init"; const CrispWithNoSSR = dynamic(() => import("constants/crisp"), { ssr: false }); @@ -45,9 +48,10 @@ Router.events.on("routeChangeComplete", NProgress.done); function MyApp({ Component, pageProps }: AppProps) { return ( // - - - + // mobx root provider + + + {SITE_TITLE} @@ -64,10 +68,11 @@ function MyApp({ Component, pageProps }: AppProps) { + - - - + + + // ); } diff --git a/apps/app/store/root.ts b/apps/app/store/root.ts new file mode 100644 index 000000000..43daa32e6 --- /dev/null +++ b/apps/app/store/root.ts @@ -0,0 +1,17 @@ +// mobx lite +import { enableStaticRendering } from "mobx-react-lite"; +// store imports +import UserStore from "./user"; +import ThemeStore from "./theme"; + +enableStaticRendering(typeof window === "undefined"); + +export class RootStore { + user; + theme; + + constructor() { + this.user = new UserStore(this); + this.theme = new ThemeStore(this); + } +} diff --git a/apps/app/store/theme.ts b/apps/app/store/theme.ts new file mode 100644 index 000000000..ba84f3ba4 --- /dev/null +++ b/apps/app/store/theme.ts @@ -0,0 +1,66 @@ +// mobx +import { action, observable, makeObservable } from "mobx"; +// helper +import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; +// interfaces +import { ICurrentUserSettings } from "types"; + +class ThemeStore { + sidebarCollapsed: boolean | null = null; + theme: string | null = null; + // root store + rootStore; + + constructor(_rootStore: any | null = null) { + makeObservable(this, { + // observable + sidebarCollapsed: observable, + theme: observable, + // action + setSidebarCollapsed: action, + setTheme: action, + // computed + }); + + this.rootStore = _rootStore; + this.initialLoad(); + } + + setSidebarCollapsed(collapsed: boolean | null = null) { + if (collapsed === null) { + let _sidebarCollapsed: string | boolean | null = + localStorage.getItem("app_sidebar_collapsed"); + _sidebarCollapsed = _sidebarCollapsed ? (_sidebarCollapsed === "true" ? true : false) : false; + this.sidebarCollapsed = _sidebarCollapsed; + } else { + this.sidebarCollapsed = collapsed; + localStorage.setItem("app_sidebar_collapsed", collapsed.toString()); + } + } + + setTheme = async (_theme: ICurrentUserSettings) => { + try { + localStorage.setItem("theme", _theme.theme.toString()); + this.theme = _theme.theme.toString(); + + if (this.theme === "custom") { + let themeSettings = this.rootStore.user.currentUserSettings || null; + if (themeSettings && themeSettings.theme.palette) { + applyTheme( + themeSettings.theme.palette !== ",,,," + ? themeSettings.theme.palette + : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", + themeSettings.theme.darkPalette + ); + } + } else unsetCustomCssVariables(); + } catch (error) { + console.error("setting user theme error", error); + } + }; + + // init load + initialLoad() {} +} + +export default ThemeStore; diff --git a/apps/app/store/user.ts b/apps/app/store/user.ts new file mode 100644 index 000000000..dde652656 --- /dev/null +++ b/apps/app/store/user.ts @@ -0,0 +1,106 @@ +// mobx +import { action, observable, computed, makeObservable } from "mobx"; +// services +import UserService from "services/user.service"; +// interfaces +import { ICurrentUser, ICurrentUserSettings } from "types/users"; + +class UserStore { + currentUser: ICurrentUser | null = null; + currentUserSettings: ICurrentUserSettings | null = null; + // root store + rootStore; + + constructor(_rootStore: any) { + makeObservable(this, { + // observable + currentUser: observable.ref, + currentUserSettings: observable.ref, + // action + setCurrentUser: action, + setCurrentUserSettings: action, + updateCurrentUser: action, + updateCurrentUserSettings: action, + // computed + }); + this.rootStore = _rootStore; + this.initialLoad(); + } + + setCurrentUser = async () => { + try { + let userResponse: ICurrentUser | null = await UserService.currentUser(); + userResponse = userResponse || null; + if (userResponse) { + this.currentUser = { + id: userResponse?.id, + avatar: userResponse?.avatar, + first_name: userResponse?.first_name, + last_name: userResponse?.last_name, + username: userResponse?.username, + email: userResponse?.email, + mobile_number: userResponse?.mobile_number, + is_email_verified: userResponse?.is_email_verified, + is_tour_completed: userResponse?.is_tour_completed, + onboarding_step: userResponse?.onboarding_step, + is_onboarded: userResponse?.is_onboarded, + role: userResponse?.role, + }; + } + } catch (error) { + console.error("Fetching current user error", error); + } + }; + + setCurrentUserSettings = async () => { + try { + let userSettingsResponse: ICurrentUserSettings | null = await UserService.currentUser(); + userSettingsResponse = userSettingsResponse || null; + if (userSettingsResponse) { + this.currentUserSettings = { + theme: userSettingsResponse?.theme, + }; + this.rootStore.theme.setTheme(); + } + } catch (error) { + console.error("Fetching current user error", error); + } + }; + + updateCurrentUser = async (user: ICurrentUser) => { + try { + let userResponse: ICurrentUser = await UserService.updateUser(user); + userResponse = userResponse || null; + if (userResponse) { + this.currentUser = userResponse; + return userResponse; + } + } catch (error) { + console.error("Updating user error", error); + return error; + } + }; + + updateCurrentUserSettings = async (userTheme: ICurrentUserSettings) => { + try { + let userSettingsResponse: ICurrentUserSettings = await UserService.updateUser(userTheme); + userSettingsResponse = userSettingsResponse || null; + if (userSettingsResponse) { + this.currentUserSettings = userSettingsResponse; + this.rootStore.theme.setTheme(userTheme); + return userSettingsResponse; + } + } catch (error) { + console.error("Updating user settings error", error); + return error; + } + }; + + // init load + initialLoad() { + this.setCurrentUser(); + this.setCurrentUserSettings(); + } +} + +export default UserStore; diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index ec77df242..5b174cc5a 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -38,17 +38,6 @@ export interface IUser { [...rest: string]: any; } -export interface ICustomTheme { - background: string; - text: string; - primary: string; - sidebarBackground: string; - sidebarText: string; - darkPalette: boolean; - palette: string; - theme: string; -} - export interface ICurrentUserResponse extends IUser { assigned_issues: number; last_workspace_id: string | null; @@ -158,3 +147,33 @@ export interface IUserProfileProjectSegregation { user_timezone: string; }; } + +export interface ICurrentUser { + id: readonly string; + avatar: string; + first_name: string; + last_name: string; + username: string; + email: string; + mobile_number: string; + is_email_verified: boolean; + is_tour_completed: boolean; + onboarding_step: TOnboardingSteps; + is_onboarded: boolean; + role: string; +} + +export interface ICustomTheme { + background: string; + text: string; + primary: string; + sidebarBackground: string; + sidebarText: string; + darkPalette: boolean; + palette: string; + theme: string; +} + +export interface ICurrentUserSettings { + theme: ICustomTheme; +} diff --git a/yarn.lock b/yarn.lock index a81c5c77d..a11b49918 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6673,6 +6673,18 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" +mobx-react-lite@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-4.0.3.tgz#f7aa5ac3be558ca19a53b2929d9599679769c2a8" + integrity sha512-wEE1oT5zvDdvplG4HnRrFgPwg5GFVVrEtl42Er85k23zeu3om8H8wbDPgdbQP88zAihVsik6xJfw6VnzUl8fQw== + dependencies: + use-sync-external-store "^1.2.0" + +mobx@^6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.10.0.tgz#3537680fe98d45232cc19cc8f76280bd8bb6b0b7" + integrity sha512-WMbVpCMFtolbB8swQ5E2YRrU+Yu8iLozCVx3CdGjbBKlP7dFiCSuiG06uea3JCFN5DnvtAX7+G5Bp82e2xu0ww== + moo@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c" From 9df0ba6e3adc31b350bc99e75dc895140935f858 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 8 Aug 2023 12:55:42 +0530 Subject: [PATCH 14/82] feat: initiated plane space (#1801) --- apps/space/.env.example | 1 + apps/space/.gitignore | 39 +++++++++++++++++++ apps/space/.prettierignore | 2 + apps/space/.prettierrc.json | 7 ++++ apps/space/README.md | 10 +++++ .../app/[workspace_project_slug]/page.tsx | 9 +++++ apps/space/app/layout.tsx | 12 ++++++ apps/space/app/page.tsx | 7 ++++ apps/space/lib/mobx-store/root.ts | 1 + apps/space/next.config.js | 9 +++++ apps/space/package.json | 35 +++++++++++++++++ apps/space/postcss.config.js | 6 +++ apps/space/store/root.ts | 1 + apps/space/styles/globals.css | 6 +++ apps/space/tailwind.config.js | 16 ++++++++ apps/space/tsconfig.json | 23 +++++++++++ 16 files changed, 184 insertions(+) create mode 100644 apps/space/.env.example create mode 100644 apps/space/.gitignore create mode 100644 apps/space/.prettierignore create mode 100644 apps/space/.prettierrc.json create mode 100644 apps/space/README.md create mode 100644 apps/space/app/[workspace_project_slug]/page.tsx create mode 100644 apps/space/app/layout.tsx create mode 100644 apps/space/app/page.tsx create mode 100644 apps/space/lib/mobx-store/root.ts create mode 100644 apps/space/next.config.js create mode 100644 apps/space/package.json create mode 100644 apps/space/postcss.config.js create mode 100644 apps/space/store/root.ts create mode 100644 apps/space/styles/globals.css create mode 100644 apps/space/tailwind.config.js create mode 100644 apps/space/tsconfig.json diff --git a/apps/space/.env.example b/apps/space/.env.example new file mode 100644 index 000000000..7cecf3739 --- /dev/null +++ b/apps/space/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_VERCEL_ENV=local \ No newline at end of file diff --git a/apps/space/.gitignore b/apps/space/.gitignore new file mode 100644 index 000000000..a2a963ee7 --- /dev/null +++ b/apps/space/.gitignore @@ -0,0 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# env +.env diff --git a/apps/space/.prettierignore b/apps/space/.prettierignore new file mode 100644 index 000000000..b9dc03452 --- /dev/null +++ b/apps/space/.prettierignore @@ -0,0 +1,2 @@ +.next +.vercel \ No newline at end of file diff --git a/apps/space/.prettierrc.json b/apps/space/.prettierrc.json new file mode 100644 index 000000000..4291d9f84 --- /dev/null +++ b/apps/space/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "semi": true, + "singleQuote": false, + "trailingComma": "es5" +} diff --git a/apps/space/README.md b/apps/space/README.md new file mode 100644 index 000000000..fc2298101 --- /dev/null +++ b/apps/space/README.md @@ -0,0 +1,10 @@ +

+ +

+ + Plane Logo + +

+ +

Plane Space

+

Open-source, self-hosted project planning tool

diff --git a/apps/space/app/[workspace_project_slug]/page.tsx b/apps/space/app/[workspace_project_slug]/page.tsx new file mode 100644 index 000000000..638d36e77 --- /dev/null +++ b/apps/space/app/[workspace_project_slug]/page.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +const WorkspaceProjectPage = () => ( +
+ Plane Workspace project Space +
+); + +export default WorkspaceProjectPage; diff --git a/apps/space/app/layout.tsx b/apps/space/app/layout.tsx new file mode 100644 index 000000000..5c7de32ff --- /dev/null +++ b/apps/space/app/layout.tsx @@ -0,0 +1,12 @@ +// root styles +import "styles/globals.css"; + +const RootLayout = ({ children }: { children: React.ReactNode }) => ( + + +
{children}
+ + +); + +export default RootLayout; diff --git a/apps/space/app/page.tsx b/apps/space/app/page.tsx new file mode 100644 index 000000000..bbfe9c3ea --- /dev/null +++ b/apps/space/app/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const HomePage = () => ( +
Plane Space
+); + +export default HomePage; diff --git a/apps/space/lib/mobx-store/root.ts b/apps/space/lib/mobx-store/root.ts new file mode 100644 index 000000000..a10356821 --- /dev/null +++ b/apps/space/lib/mobx-store/root.ts @@ -0,0 +1 @@ +export const init = {}; diff --git a/apps/space/next.config.js b/apps/space/next.config.js new file mode 100644 index 000000000..398eb0400 --- /dev/null +++ b/apps/space/next.config.js @@ -0,0 +1,9 @@ +/** @type {import('next').NextConfig} */ + +const nextConfig = { + experimental: { + appDir: true, + }, +}; + +module.exports = nextConfig; diff --git a/apps/space/package.json b/apps/space/package.json new file mode 100644 index 000000000..7ace168aa --- /dev/null +++ b/apps/space/package.json @@ -0,0 +1,35 @@ +{ + "name": "plane-space", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@headlessui/react": "^1.7.13", + "@types/node": "18.14.1", + "@types/nprogress": "^0.2.0", + "@types/react": "18.0.28", + "@types/react-dom": "18.0.11", + "axios": "^1.3.4", + "eslint": "8.34.0", + "eslint-config-next": "13.2.1", + "js-cookie": "^3.0.1", + "next": "^13.4.13", + "nprogress": "^0.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "4.9.5", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/js-cookie": "^3.0.3", + "@types/uuid": "^9.0.1", + "autoprefixer": "^10.4.13", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7" + } +} diff --git a/apps/space/postcss.config.js b/apps/space/postcss.config.js new file mode 100644 index 000000000..12a703d90 --- /dev/null +++ b/apps/space/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/space/store/root.ts b/apps/space/store/root.ts new file mode 100644 index 000000000..a10356821 --- /dev/null +++ b/apps/space/store/root.ts @@ -0,0 +1 @@ +export const init = {}; diff --git a/apps/space/styles/globals.css b/apps/space/styles/globals.css new file mode 100644 index 000000000..e493f0abc --- /dev/null +++ b/apps/space/styles/globals.css @@ -0,0 +1,6 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,0,0&display=swap"); + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/apps/space/tailwind.config.js b/apps/space/tailwind.config.js new file mode 100644 index 000000000..145c65b5a --- /dev/null +++ b/apps/space/tailwind.config.js @@ -0,0 +1,16 @@ +/** @type {import('tailwindcss').Config} */ + +module.exports = { + content: [ + "./app/**/*.{js,ts,jsx,tsx}", + "./pages/**/*.{js,ts,jsx,tsx}", + "./layouts/**/*.tsx", + "./components/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: {}, + }, + }, + plugins: [], +}; diff --git a/apps/space/tsconfig.json b/apps/space/tsconfig.json new file mode 100644 index 000000000..5404bd9fb --- /dev/null +++ b/apps/space/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "baseUrl": ".", + "paths": {}, + "plugins": [{ "name": "next" }] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "server.js", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From cf306ee605880ac549333c17d47f6d0dda461255 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:59:04 +0530 Subject: [PATCH 15/82] feat: user display name (#1179) * feat: user display name for the entire system * feat: update issue activity to remove emails * dev: update to display name wherever assignees__email and member__email * dev: update display names on issue activity and the user script * dev: update display_name function to generate display_name from email * dev: add email for test purpose * dev: set default display name for the user * dev: add migration script and default value * dev: annotate with assignees_id * dev: return assignees id * dev: display name for the profile * dev: project members endpoint * dev: url update * dev: trailing / * dev: update workspace member serializer * fix: activity for assignees --- apiserver/bin/user_script.py | 8 +- apiserver/plane/api/serializers/__init__.py | 8 +- apiserver/plane/api/serializers/people.py | 57 ---------- apiserver/plane/api/serializers/project.py | 13 ++- apiserver/plane/api/serializers/user.py | 44 +++++++- apiserver/plane/api/serializers/workspace.py | 35 +++--- apiserver/plane/api/urls.py | 12 +++ apiserver/plane/api/views/__init__.py | 4 +- apiserver/plane/api/views/analytic.py | 10 +- apiserver/plane/api/views/auth_extended.py | 2 +- apiserver/plane/api/views/importer.py | 2 +- apiserver/plane/api/views/page.py | 2 +- apiserver/plane/api/views/project.py | 24 ++++- .../plane/api/views/{people.py => user.py} | 0 apiserver/plane/api/views/workspace.py | 48 +++++++-- .../plane/bgtasks/analytic_plot_export.py | 20 ++-- .../plane/bgtasks/issue_activites_task.py | 84 +++++++-------- ..._alter_analyticview_created_by_and_more.py | 101 ++++++++++++++++++ apiserver/plane/db/models/user.py | 13 ++- 19 files changed, 330 insertions(+), 157 deletions(-) delete mode 100644 apiserver/plane/api/serializers/people.py rename apiserver/plane/api/views/{people.py => user.py} (100%) create mode 100644 apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py diff --git a/apiserver/bin/user_script.py b/apiserver/bin/user_script.py index b554d2c40..e115b20b8 100644 --- a/apiserver/bin/user_script.py +++ b/apiserver/bin/user_script.py @@ -1,4 +1,4 @@ -import os, sys +import os, sys, random, string import uuid sys.path.append("/code") @@ -19,9 +19,9 @@ def populate(): user = User.objects.create(email=default_email, username=uuid.uuid4().hex) user.set_password(default_password) user.save() - print("User created") - - print("Success") + print(f"User created with an email: {default_email}") + else: + print(f"User already exists with the default email: {default_email}") if __name__ == "__main__": diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 2d38b1139..683ed9670 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -1,10 +1,5 @@ from .base import BaseSerializer -from .people import ( - ChangePasswordSerializer, - ResetPasswordSerializer, - TokenSerializer, -) -from .user import UserSerializer, UserLiteSerializer +from .user import UserSerializer, UserLiteSerializer, ChangePasswordSerializer, ResetPasswordSerializer, UserAdminLiteSerializer from .workspace import ( WorkSpaceSerializer, WorkSpaceMemberSerializer, @@ -12,6 +7,7 @@ from .workspace import ( WorkSpaceMemberInviteSerializer, WorkspaceLiteSerializer, WorkspaceThemeSerializer, + WorkspaceMemberAdminSerializer, ) from .project import ( ProjectSerializer, diff --git a/apiserver/plane/api/serializers/people.py b/apiserver/plane/api/serializers/people.py deleted file mode 100644 index b8b59416c..000000000 --- a/apiserver/plane/api/serializers/people.py +++ /dev/null @@ -1,57 +0,0 @@ -from rest_framework.serializers import ( - ModelSerializer, - Serializer, - CharField, - SerializerMethodField, -) -from rest_framework.authtoken.models import Token -from rest_framework_simplejwt.tokens import RefreshToken - - -from plane.db.models import User - - -class UserSerializer(ModelSerializer): - class Meta: - model = User - fields = "__all__" - extra_kwargs = {"password": {"write_only": True}} - - -class ChangePasswordSerializer(Serializer): - model = User - - """ - Serializer for password change endpoint. - """ - old_password = CharField(required=True) - new_password = CharField(required=True) - - -class ResetPasswordSerializer(Serializer): - model = User - - """ - Serializer for password change endpoint. - """ - new_password = CharField(required=True) - confirm_password = CharField(required=True) - - -class TokenSerializer(ModelSerializer): - - user = UserSerializer() - access_token = SerializerMethodField() - refresh_token = SerializerMethodField() - - def get_access_token(self, obj): - refresh_token = RefreshToken.for_user(obj.user) - return str(refresh_token.access_token) - - def get_refresh_token(self, obj): - refresh_token = RefreshToken.for_user(obj.user) - return str(refresh_token) - - class Meta: - model = Token - fields = "__all__" diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index fa97c5a6d..643518daa 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -7,7 +7,7 @@ from rest_framework import serializers # Module imports from .base import BaseSerializer from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer -from plane.api.serializers.user import UserLiteSerializer +from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( Project, ProjectMember, @@ -110,6 +110,17 @@ class ProjectMemberSerializer(BaseSerializer): fields = "__all__" +class ProjectMemberAdminSerializer(BaseSerializer): + workspace = WorkspaceLiteSerializer(read_only=True) + project = ProjectLiteSerializer(read_only=True) + member = UserAdminLiteSerializer(read_only=True) + + + class Meta: + model = ProjectMember + fields = "__all__" + + class ProjectMemberInviteSerializer(BaseSerializer): project = ProjectLiteSerializer(read_only=True) workspace = WorkspaceLiteSerializer(read_only=True) diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/api/serializers/user.py index d8978479e..dcb00c6cb 100644 --- a/apiserver/plane/api/serializers/user.py +++ b/apiserver/plane/api/serializers/user.py @@ -1,3 +1,6 @@ +# Third party imports +from rest_framework import serializers + # Module import from .base import BaseSerializer from plane.db.models import User @@ -37,11 +40,50 @@ class UserLiteSerializer(BaseSerializer): "id", "first_name", "last_name", - "email", "avatar", "is_bot", + "display_name", ] read_only_fields = [ "id", "is_bot", ] + + +class UserAdminLiteSerializer(BaseSerializer): + + class Meta: + model = User + fields = [ + "id", + "first_name", + "last_name", + "avatar", + "is_bot", + "display_name", + "email", + ] + read_only_fields = [ + "id", + "is_bot", + ] + + +class ChangePasswordSerializer(serializers.Serializer): + model = User + + """ + Serializer for password change endpoint. + """ + old_password = serializers.CharField(required=True) + new_password = serializers.CharField(required=True) + + +class ResetPasswordSerializer(serializers.Serializer): + model = User + + """ + Serializer for password change endpoint. + """ + new_password = serializers.CharField(required=True) + confirm_password = serializers.CharField(required=True) diff --git a/apiserver/plane/api/serializers/workspace.py b/apiserver/plane/api/serializers/workspace.py index 4d83d6262..d27b66481 100644 --- a/apiserver/plane/api/serializers/workspace.py +++ b/apiserver/plane/api/serializers/workspace.py @@ -3,7 +3,7 @@ from rest_framework import serializers # Module imports from .base import BaseSerializer -from .user import UserLiteSerializer +from .user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( User, @@ -33,10 +33,30 @@ class WorkSpaceSerializer(BaseSerializer): "owner", ] +class WorkspaceLiteSerializer(BaseSerializer): + class Meta: + model = Workspace + fields = [ + "name", + "slug", + "id", + ] + read_only_fields = fields + + class WorkSpaceMemberSerializer(BaseSerializer): member = UserLiteSerializer(read_only=True) - workspace = WorkSpaceSerializer(read_only=True) + workspace = WorkspaceLiteSerializer(read_only=True) + + class Meta: + model = WorkspaceMember + fields = "__all__" + + +class WorkspaceMemberAdminSerializer(BaseSerializer): + member = UserAdminLiteSerializer(read_only=True) + workspace = WorkspaceLiteSerializer(read_only=True) class Meta: model = WorkspaceMember @@ -101,17 +121,6 @@ class TeamSerializer(BaseSerializer): return super().update(instance, validated_data) -class WorkspaceLiteSerializer(BaseSerializer): - class Meta: - model = Workspace - fields = [ - "name", - "slug", - "id", - ] - read_only_fields = fields - - class WorkspaceThemeSerializer(BaseSerializer): class Meta: model = WorkspaceTheme diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 2d1b2c908..b1231f1a4 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -32,6 +32,7 @@ from plane.api.views import ( InviteWorkspaceEndpoint, JoinWorkspaceEndpoint, WorkSpaceMemberViewSet, + WorkspaceMembersEndpoint, WorkspaceInvitationsViewset, UserWorkspaceInvitationsEndpoint, WorkspaceMemberUserEndpoint, @@ -59,6 +60,7 @@ from plane.api.views import ( ProjectViewSet, InviteProjectEndpoint, ProjectMemberViewSet, + ProjectMemberEndpoint, ProjectMemberInvitationsViewset, ProjectMemberUserEndpoint, AddMemberToProjectEndpoint, @@ -335,6 +337,11 @@ urlpatterns = [ ), name="workspace", ), + path( + "workspaces//workspace-members/", + WorkspaceMembersEndpoint.as_view(), + name="workspace-members", + ), path( "workspaces//teams/", TeamMemberViewSet.as_view( @@ -468,6 +475,11 @@ urlpatterns = [ ), name="project", ), + path( + "workspaces//projects//project-members/", + ProjectMemberEndpoint.as_view(), + name="project", + ), path( "workspaces//projects//members/add/", AddMemberToProjectEndpoint.as_view(), diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 92b647a97..a02e22fe9 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -12,8 +12,9 @@ from .project import ( ProjectUserViewsEndpoint, ProjectMemberUserEndpoint, ProjectFavoritesViewSet, + ProjectMemberEndpoint, ) -from .people import ( +from .user import ( UserEndpoint, UpdateUserOnBoardedEndpoint, UpdateUserTourCompletedEndpoint, @@ -47,6 +48,7 @@ from .workspace import ( WorkspaceUserProfileEndpoint, WorkspaceUserProfileIssuesEndpoint, WorkspaceLabelsEndpoint, + WorkspaceMembersEndpoint, ) from .state import StateViewSet from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet diff --git a/apiserver/plane/api/views/analytic.py b/apiserver/plane/api/views/analytic.py index e537af84a..7d5786c19 100644 --- a/apiserver/plane/api/views/analytic.py +++ b/apiserver/plane/api/views/analytic.py @@ -79,12 +79,12 @@ class AnalyticsEndpoint(BaseAPIView): ) assignee_details = {} - if x_axis in ["assignees__email"] or segment in ["assignees__email"]: + if x_axis in ["assignees__id"] or segment in ["assignees__id"]: assignee_details = ( Issue.issue_objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False) .order_by("assignees__id") .distinct("assignees__id") - .values("assignees__avatar", "assignees__email", "assignees__first_name", "assignees__last_name") + .values("assignees__avatar", "assignees__display_name", "assignees__first_name", "assignees__last_name", "assignees__id") ) @@ -243,21 +243,21 @@ class DefaultAnalyticsEndpoint(BaseAPIView): ) most_issue_created_user = ( queryset.exclude(created_by=None) - .values("created_by__first_name", "created_by__last_name", "created_by__avatar", "created_by__email") + .values("created_by__first_name", "created_by__last_name", "created_by__avatar", "created_by__display_name") .annotate(count=Count("id")) .order_by("-count") )[:5] most_issue_closed_user = ( queryset.filter(completed_at__isnull=False, assignees__isnull=False) - .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__email") + .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__display_name") .annotate(count=Count("id")) .order_by("-count") )[:5] pending_issue_user = ( queryset.filter(completed_at__isnull=True) - .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__email") + .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__display_name") .annotate(count=Count("id")) .order_by("-count") ) diff --git a/apiserver/plane/api/views/auth_extended.py b/apiserver/plane/api/views/auth_extended.py index 56dc091f4..df3f3aaca 100644 --- a/apiserver/plane/api/views/auth_extended.py +++ b/apiserver/plane/api/views/auth_extended.py @@ -22,7 +22,7 @@ from sentry_sdk import capture_exception ## Module imports from . import BaseAPIView -from plane.api.serializers.people import ( +from plane.api.serializers import ( ChangePasswordSerializer, ResetPasswordSerializer, ) diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/api/views/importer.py index 4fc7ad483..0a92b3850 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/api/views/importer.py @@ -458,7 +458,7 @@ class BulkImportIssuesEndpoint(BaseAPIView): actor=request.user, project_id=project_id, workspace_id=project.workspace_id, - comment=f"{request.user.email} importer the issue from {service}", + comment=f"imported the issue from {service}", verb="created", created_by=request.user, ) diff --git a/apiserver/plane/api/views/page.py b/apiserver/plane/api/views/page.py index edca47ffe..d9fad9eaa 100644 --- a/apiserver/plane/api/views/page.py +++ b/apiserver/plane/api/views/page.py @@ -301,7 +301,7 @@ class CreateIssueFromPageBlockEndpoint(BaseAPIView): issue=issue, actor=request.user, project_id=project_id, - comment=f"{request.user.email} created the issue from {page_block.name} block", + comment=f"created the issue from {page_block.name} block", verb="created", ) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 31741f10c..98484f74b 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -25,7 +25,7 @@ from plane.api.serializers import ( ProjectFavoriteSerializer, ) -from plane.api.permissions import ProjectBasePermission +from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission from plane.db.models import ( Project, @@ -458,7 +458,7 @@ class ProjectMemberViewSet(BaseViewSet): ] search_fields = [ - "member__email", + "member__display_name", "member__first_name", ] @@ -984,3 +984,23 @@ class ProjectFavoritesViewSet(BaseViewSet): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class ProjectMemberEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id): + try: + project_members = ProjectMember.objects.filter( + project_id=project_id, workspace__slug=slug + ).select_related("project", "member") + serializer = ProjectMemberSerializer(project_members, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/api/views/people.py b/apiserver/plane/api/views/user.py similarity index 100% rename from apiserver/plane/api/views/people.py rename to apiserver/plane/api/views/user.py diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index a862c0b4c..3957bcae0 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -47,6 +47,7 @@ from plane.api.serializers import ( WorkspaceThemeSerializer, IssueActivitySerializer, IssueLiteSerializer, + WorkspaceMemberAdminSerializer ) from plane.api.views.base import BaseAPIView from . import BaseViewSet @@ -537,7 +538,7 @@ class UserWorkspaceInvitationsEndpoint(BaseViewSet): class WorkSpaceMemberViewSet(BaseViewSet): - serializer_class = WorkSpaceMemberSerializer + serializer_class = WorkspaceMemberAdminSerializer model = WorkspaceMember permission_classes = [ @@ -545,7 +546,7 @@ class WorkSpaceMemberViewSet(BaseViewSet): ] search_fields = [ - "member__email", + "member__display_name", "member__first_name", ] @@ -690,7 +691,7 @@ class TeamMemberViewSet(BaseViewSet): ] search_fields = [ - "member__email", + "member__display_name", "member__first_name", ] @@ -1048,7 +1049,6 @@ class WorkspaceThemeViewSet(BaseViewSet): class WorkspaceUserProfileStatsEndpoint(BaseAPIView): - def get(self, request, slug, user_id): try: filters = issue_filters(request.query_params, "GET") @@ -1146,14 +1146,18 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): upcoming_cycles = CycleIssue.objects.filter( workspace__slug=slug, cycle__start_date__gt=timezone.now().date(), - issue__assignees__in=[user_id,] + issue__assignees__in=[ + user_id, + ], ).values("cycle__name", "cycle__id", "cycle__project_id") present_cycle = CycleIssue.objects.filter( workspace__slug=slug, cycle__start_date__lt=timezone.now().date(), cycle__end_date__gt=timezone.now().date(), - issue__assignees__in=[user_id,] + issue__assignees__in=[ + user_id, + ], ).values("cycle__name", "cycle__id", "cycle__project_id") return Response( @@ -1166,7 +1170,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): "pending_issues": pending_issues_count, "subscribed_issues": subscribed_issues_count, "present_cycles": present_cycle, - "upcoming_cycles": upcoming_cycles, + "upcoming_cycles": upcoming_cycles, } ) except Exception as e: @@ -1184,7 +1188,6 @@ class WorkspaceUserActivityEndpoint(BaseAPIView): def get(self, request, slug, user_id): try: - projects = request.query_params.getlist("project", []) queryset = IssueActivity.objects.filter( @@ -1212,12 +1215,13 @@ class WorkspaceUserActivityEndpoint(BaseAPIView): class WorkspaceUserProfileEndpoint(BaseAPIView): - def get(self, request, slug, user_id): try: user_data = User.objects.get(pk=user_id) - requesting_workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user) + requesting_workspace_member = WorkspaceMember.objects.get( + workspace__slug=slug, member=request.user + ) projects = [] if requesting_workspace_member.role >= 10: projects = ( @@ -1227,7 +1231,8 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): ) .annotate( created_issues=Count( - "project_issue", filter=Q(project_issue__created_by_id=user_id) + "project_issue", + filter=Q(project_issue__created_by_id=user_id), ) ) .annotate( @@ -1282,6 +1287,7 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): "cover_image": user_data.cover_image, "date_joined": user_data.date_joined, "user_timezone": user_data.user_timezone, + "display_name": user_data.display_name, }, }, status=status.HTTP_200_OK, @@ -1439,3 +1445,23 @@ class WorkspaceLabelsEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class WorkspaceMembersEndpoint(BaseAPIView): + permission_classes = [ + WorkspaceEntityPermission, + ] + + def get(self, request, slug): + try: + workspace_members = WorkspaceMember.objects.filter( + workspace__slug=slug + ).select_related("workspace", "member") + serialzier = WorkSpaceMemberSerializer(workspace_members, many=True) + return Response(serialzier.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/bgtasks/analytic_plot_export.py b/apiserver/plane/bgtasks/analytic_plot_export.py index 27b625445..3177c39fe 100644 --- a/apiserver/plane/bgtasks/analytic_plot_export.py +++ b/apiserver/plane/bgtasks/analytic_plot_export.py @@ -21,7 +21,7 @@ row_mapping = { "state__name": "State", "state__group": "State Group", "labels__name": "Label", - "assignees__email": "Assignee Name", + "assignees__display_name": "Assignee Name", "start_date": "Start Date", "target_date": "Due Date", "completed_at": "Completed At", @@ -51,12 +51,12 @@ def analytic_export_task(email, data, slug): segmented = segment assignee_details = {} - if x_axis in ["assignees__email"] or segment in ["assignees__email"]: + if x_axis in ["assignees__display_name"] or segment in ["assignees__display_name"]: assignee_details = ( Issue.issue_objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False) .order_by("assignees__id") .distinct("assignees__id") - .values("assignees__avatar", "assignees__email", "assignees__first_name", "assignees__last_name") + .values("assignees__avatar", "assignees__display_name", "assignees__first_name", "assignees__last_name") ) if segment: @@ -93,17 +93,17 @@ def analytic_export_task(email, data, slug): else: generated_row.append("0") # x-axis replacement for names - if x_axis in ["assignees__email"]: - assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(item)] + if x_axis in ["assignees__display_name"]: + assignee = [user for user in assignee_details if str(user.get("assignees__display_name")) == str(item)] if len(assignee): generated_row[0] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name")) rows.append(tuple(generated_row)) - # If segment is ["assignees__email"] then replace segment_zero rows with first and last names - if segmented in ["assignees__email"]: + # If segment is ["assignees__display_name"] then replace segment_zero rows with first and last names + if segmented in ["assignees__display_name"]: for index, segm in enumerate(row_zero[2:]): # find the name of the user - assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(segm)] + assignee = [user for user in assignee_details if str(user.get("assignees__display_name")) == str(segm)] if len(assignee): row_zero[index] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name")) @@ -141,8 +141,8 @@ def analytic_export_task(email, data, slug): else distribution.get(item)[0].get("estimate "), ] # x-axis replacement to names - if x_axis in ["assignees__email"]: - assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(item)] + if x_axis in ["assignees__display_name"]: + assignee = [user for user in assignee_details if str(user.get("assignees__display_name")) == str(item)] if len(assignee): row[0] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name")) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 8f34daf52..9150d7c94 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -48,7 +48,7 @@ def track_name( field="name", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the name to {requested_data.get('name')}", + comment=f"updated the name to {requested_data.get('name')}", ) ) @@ -75,7 +75,7 @@ def track_parent( field="parent", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the parent issue to None", + comment=f"updated the parent issue to None", old_identifier=old_parent.id, new_identifier=None, ) @@ -95,7 +95,7 @@ def track_parent( field="parent", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the parent issue to {new_parent.name}", + comment=f"updated the parent issue to {new_parent.name}", old_identifier=old_parent.id if old_parent is not None else None, new_identifier=new_parent.id, ) @@ -123,7 +123,7 @@ def track_priority( field="priority", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the priority to None", + comment=f"updated the priority to None", ) ) else: @@ -137,7 +137,7 @@ def track_priority( field="priority", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the priority to {requested_data.get('priority')}", + comment=f"updated the priority to {requested_data.get('priority')}", ) ) @@ -165,7 +165,7 @@ def track_state( field="state", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the state to {new_state.name}", + comment=f"updated the state to {new_state.name}", old_identifier=old_state.id, new_identifier=new_state.id, ) @@ -194,7 +194,7 @@ def track_description( field="description", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the description to {requested_data.get('description_html')}", + comment=f"updated the description to {requested_data.get('description_html')}", ) ) @@ -220,7 +220,7 @@ def track_target_date( field="target_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the target date to None", + comment=f"updated the target date to None", ) ) else: @@ -234,7 +234,7 @@ def track_target_date( field="target_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the target date to {requested_data.get('target_date')}", + comment=f"updated the target date to {requested_data.get('target_date')}", ) ) @@ -260,7 +260,7 @@ def track_start_date( field="start_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the start date to None", + comment=f"updated the start date to None", ) ) else: @@ -274,7 +274,7 @@ def track_start_date( field="start_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the start date to {requested_data.get('start_date')}", + comment=f"updated the start date to {requested_data.get('start_date')}", ) ) @@ -303,7 +303,7 @@ def track_labels( field="labels", project=project, workspace=project.workspace, - comment=f"{actor.email} added label {label.name}", + comment=f"added label {label.name}", new_identifier=label.id, old_identifier=None, ) @@ -324,7 +324,7 @@ def track_labels( field="labels", project=project, workspace=project.workspace, - comment=f"{actor.email} removed label {label.name}", + comment=f"removed label {label.name}", old_identifier=label.id, new_identifier=None, ) @@ -353,12 +353,12 @@ def track_assignees( actor=actor, verb="updated", old_value="", - new_value=assignee.email, + new_value=assignee.display_name, field="assignees", project=project, workspace=project.workspace, - comment=f"{actor.email} added assignee {assignee.email}", - new_identifier=actor.id, + comment=f"added assignee {assignee.display_name}", + new_identifier=assignee.id, ) ) @@ -374,13 +374,13 @@ def track_assignees( issue_id=issue_id, actor=actor, verb="updated", - old_value=assignee.email, + old_value=assignee.display_name, new_value="", field="assignees", project=project, workspace=project.workspace, - comment=f"{actor.email} removed assignee {assignee.email}", - old_identifier=actor.id, + comment=f"removed assignee {assignee.display_name}", + old_identifier=assignee.id, ) ) @@ -419,7 +419,7 @@ def track_blocks( field="blocks", project=project, workspace=project.workspace, - comment=f"{actor.email} added blocking issue {issue.project.identifier}-{issue.sequence_id}", + comment=f"added blocking issue {project.identifier}-{issue.sequence_id}", new_identifier=issue.id, ) ) @@ -441,7 +441,7 @@ def track_blocks( field="blocks", project=project, workspace=project.workspace, - comment=f"{actor.email} removed blocking issue {issue.project.identifier}-{issue.sequence_id}", + comment=f"removed blocking issue {project.identifier}-{issue.sequence_id}", old_identifier=issue.id, ) ) @@ -481,7 +481,7 @@ def track_blockings( field="blocking", project=project, workspace=project.workspace, - comment=f"{actor.email} added blocked by issue {issue.project.identifier}-{issue.sequence_id}", + comment=f"added blocked by issue {project.identifier}-{issue.sequence_id}", new_identifier=issue.id, ) ) @@ -503,7 +503,7 @@ def track_blockings( field="blocking", project=project, workspace=project.workspace, - comment=f"{actor.email} removed blocked by issue {issue.project.identifier}-{issue.sequence_id}", + comment=f"removed blocked by issue {project.identifier}-{issue.sequence_id}", old_identifier=issue.id, ) ) @@ -517,7 +517,7 @@ def create_issue_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created the issue", + comment=f"created the issue", verb="created", actor=actor, ) @@ -539,7 +539,7 @@ def track_estimate_points( field="estimate_point", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the estimate point to None", + comment=f"updated the estimate point to None", ) ) else: @@ -553,7 +553,7 @@ def track_estimate_points( field="estimate_point", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the estimate point to {requested_data.get('estimate_point')}", + comment=f"updated the estimate point to {requested_data.get('estimate_point')}", ) ) @@ -567,7 +567,7 @@ def track_archive_at( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} has restored the issue", + comment=f"has restored the issue", verb="updated", actor=actor, field="archived_at", @@ -661,7 +661,7 @@ def delete_issue_activity( IssueActivity( project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the issue", + comment=f"deleted the issue", verb="deleted", actor=actor, field="issue", @@ -682,7 +682,7 @@ def create_comment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created a comment", + comment=f"created a comment", verb="created", actor=actor, field="comment", @@ -707,7 +707,7 @@ def update_comment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} updated a comment", + comment=f"updated a comment", verb="updated", actor=actor, field="comment", @@ -728,7 +728,7 @@ def delete_comment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the comment", + comment=f"deleted the comment", verb="deleted", actor=actor, field="comment", @@ -766,7 +766,7 @@ def create_cycle_issue_activity( field="cycles", project=project, workspace=project.workspace, - comment=f"{actor.email} updated cycle from {old_cycle.name} to {new_cycle.name}", + comment=f"updated cycle from {old_cycle.name} to {new_cycle.name}", old_identifier=old_cycle.id, new_identifier=new_cycle.id, ) @@ -787,7 +787,7 @@ def create_cycle_issue_activity( field="cycles", project=project, workspace=project.workspace, - comment=f"{actor.email} added cycle {cycle.name}", + comment=f"added cycle {cycle.name}", new_identifier=cycle.id, ) ) @@ -816,7 +816,7 @@ def delete_cycle_issue_activity( field="cycles", project=project, workspace=project.workspace, - comment=f"{actor.email} removed this issue from {cycle.name if cycle is not None else None}", + comment=f"removed this issue from {cycle.name if cycle is not None else None}", old_identifier=cycle.id if cycle is not None else None, ) ) @@ -852,7 +852,7 @@ def create_module_issue_activity( field="modules", project=project, workspace=project.workspace, - comment=f"{actor.email} updated module from {old_module.name} to {new_module.name}", + comment=f"updated module from {old_module.name} to {new_module.name}", old_identifier=old_module.id, new_identifier=new_module.id, ) @@ -872,7 +872,7 @@ def create_module_issue_activity( field="modules", project=project, workspace=project.workspace, - comment=f"{actor.email} added module {module.name}", + comment=f"added module {module.name}", new_identifier=module.id, ) ) @@ -901,7 +901,7 @@ def delete_module_issue_activity( field="modules", project=project, workspace=project.workspace, - comment=f"{actor.email} removed this issue from {module.name if module is not None else None}", + comment=f"removed this issue from {module.name if module is not None else None}", old_identifier=module.id if module is not None else None, ) ) @@ -920,7 +920,7 @@ def create_link_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created a link", + comment=f"created a link", verb="created", actor=actor, field="link", @@ -944,7 +944,7 @@ def update_link_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} updated a link", + comment=f"updated a link", verb="updated", actor=actor, field="link", @@ -969,7 +969,7 @@ def delete_link_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the link", + comment=f"deleted the link", verb="deleted", actor=actor, field="link", @@ -992,7 +992,7 @@ def create_attachment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created an attachment", + comment=f"created an attachment", verb="created", actor=actor, field="attachment", @@ -1010,7 +1010,7 @@ def delete_attachment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the attachment", + comment=f"deleted the attachment", verb="deleted", actor=actor, field="attachment", diff --git a/apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py b/apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py new file mode 100644 index 000000000..0a561db5e --- /dev/null +++ b/apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py @@ -0,0 +1,101 @@ +# Generated by Django 4.2.3 on 2023-08-04 09:12 +import string +import random +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def generate_display_name(apps, schema_editor): + UserModel = apps.get_model("db", "User") + updated_users = [] + for obj in UserModel.objects.all(): + obj.display_name = ( + obj.email.split("@")[0] + if len(obj.email.split("@")) + else "".join(random.choice(string.ascii_letters) for _ in range(6)) + ) + updated_users.append(obj) + UserModel.objects.bulk_update(updated_users, ["display_name"], batch_size=100) + + +def rectify_field_issue_activity(apps, schema_editor): + Model = apps.get_model("db", "IssueActivity") + updated_activity = [] + for obj in Model.objects.filter(field="assignee"): + obj.field = "assignees" + updated_activity.append(obj) + + Model.objects.bulk_update(updated_activity, ["field"], batch_size=100) + + +def update_assignee_issue_activity(apps, schema_editor): + Model = apps.get_model("db", "IssueActivity") + updated_activity = [] + + # Get all the users + User = apps.get_model("db", "User") + users = User.objects.values("id", "email", "display_name") + + for obj in Model.objects.filter(field="assignees"): + if bool(obj.new_value) and not bool(obj.old_value): + # Get user from list + assigned_user = [ + user for user in users if user.get("email") == obj.new_value + ] + if assigned_user: + obj.new_value = assigned_user[0].get("display_name") + obj.new_identifier = assigned_user[0].get("id") + # Update the comment + words = obj.comment.split() + words[-1] = assigned_user[0].get("display_name") + obj.comment = " ".join(words) + + if bool(obj.old_value) and not bool(obj.new_value): + # Get user from list + assigned_user = [ + user for user in users if user.get("email") == obj.old_value + ] + if assigned_user: + obj.old_value = assigned_user[0].get("display_name") + obj.old_identifier = assigned_user[0].get("id") + # Update the comment + words = obj.comment.split() + words[-1] = assigned_user[0].get("display_name") + obj.comment = " ".join(words) + + updated_activity.append(obj) + + Model.objects.bulk_update( + updated_activity, + ["old_value", "new_value", "old_identifier", "new_identifier", "comment"], + batch_size=200, + ) + + +def update_name_activity(apps, schema_editor): + Model = apps.get_model("db", "IssueActivity") + update_activity = [] + for obj in Model.objects.filter(field="name"): + obj.comment = obj.comment.replace("start date", "name") + update_activity.append(obj) + + Model.objects.bulk_update(update_activity, ["comment"], batch_size=1000) + + +class Migration(migrations.Migration): + dependencies = [ + ("db", "0040_projectmember_preferences_user_cover_image_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="display_name", + field=models.CharField(default="", max_length=255), + ), + migrations.RunPython(generate_display_name), + migrations.RunPython(rectify_field_issue_activity), + migrations.RunPython(update_assignee_issue_activity), + migrations.RunPython(update_name_activity), + ] diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 0b643271e..3975a3b93 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -1,6 +1,7 @@ # Python imports -from enum import unique import uuid +import string +import random # Django imports from django.db import models @@ -18,6 +19,7 @@ from sentry_sdk import capture_exception from slack_sdk import WebClient from slack_sdk.errors import SlackApiError + def get_default_onboarding(): return { "profile_complete": False, @@ -26,6 +28,7 @@ def get_default_onboarding(): "workspace_join": False, } + class User(AbstractBaseUser, PermissionsMixin): id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True @@ -81,6 +84,7 @@ class User(AbstractBaseUser, PermissionsMixin): role = models.CharField(max_length=300, null=True, blank=True) is_bot = models.BooleanField(default=False) theme = models.JSONField(default=dict) + display_name = models.CharField(max_length=255, default="") is_tour_completed = models.BooleanField(default=False) onboarding_step = models.JSONField(default=get_default_onboarding) @@ -107,6 +111,13 @@ class User(AbstractBaseUser, PermissionsMixin): self.token = uuid.uuid4().hex + uuid.uuid4().hex self.token_updated_at = timezone.now() + if not self.display_name: + self.display_name = ( + self.email.split("@")[0] + if len(self.email.split("@")) + else "".join(random.choice(string.ascii_letters) for _ in range(6)) + ) + if self.is_superuser: self.is_staff = True From 981acc81c193f8d1aae3405b37e4c812cb923d04 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:01:43 +0530 Subject: [PATCH 16/82] fix: replaced first name, last name or email to display name (#1796) * fix: replacing first, last name and email with display name * fix: different endpoint for workspace & project member * fix: falling back to email if display_name doesn't exist --- .../custom-analytics/graph/custom-tooltip.tsx | 10 +- .../custom-analytics/graph/index.tsx | 6 +- .../analytics/custom-analytics/sidebar.tsx | 9 +- .../analytics/custom-analytics/table.tsx | 189 ++++++++---------- .../scope-and-demand/leaderboard.tsx | 10 +- .../scope-and-demand/scope-and-demand.tsx | 4 +- .../analytics/scope-and-demand/scope.tsx | 9 +- .../issue/change-issue-assignee.tsx | 7 +- .../components/core/filters/filters-list.tsx | 6 +- .../components/core/sidebar/links-list.tsx | 2 +- .../core/sidebar/sidebar-progress-stats.tsx | 3 +- .../core/views/board-view/board-header.tsx | 9 +- .../core/views/list-view/single-list.tsx | 11 +- .../cycles/active-cycle-details.tsx | 6 +- .../components/cycles/active-cycle-stats.tsx | 3 +- apps/app/components/cycles/sidebar.tsx | 6 +- .../components/cycles/single-cycle-card.tsx | 6 +- .../components/cycles/single-cycle-list.tsx | 4 +- .../integration/github/single-user-select.tsx | 13 +- .../integration/jira/import-users.tsx | 11 +- .../components/integration/single-import.tsx | 7 +- apps/app/components/issues/activity.tsx | 7 +- .../issues/attachment/attachments.tsx | 2 +- .../issues/comment/comment-card.tsx | 9 +- .../app/components/issues/select/assignee.tsx | 13 +- .../issues/sidebar-select/assignee.tsx | 13 +- .../issues/view-select/assignee.tsx | 19 +- apps/app/components/modules/select/lead.tsx | 17 +- .../app/components/modules/select/members.tsx | 11 +- .../modules/sidebar-select/select-lead.tsx | 17 +- .../modules/sidebar-select/select-members.tsx | 11 +- .../notifications/notification-card.tsx | 9 +- .../pages/single-page-detailed-item.tsx | 2 +- .../pages/single-page-list-item.tsx | 2 +- .../components/profile/overview/activity.tsx | 6 +- .../profile/profile-issues-view.tsx | 6 +- apps/app/components/profile/sidebar.tsx | 14 +- .../project/confirm-project-member-remove.tsx | 6 +- .../project/create-project-modal.tsx | 18 +- .../project/send-project-invitation-modal.tsx | 11 +- apps/app/components/search-listbox/index.tsx | 4 +- apps/app/components/ui/avatar.tsx | 6 +- apps/app/components/views/select-filters.tsx | 8 +- .../confirm-workspace-member-remove.tsx | 6 +- .../components/workspace/sidebar-dropdown.tsx | 2 +- .../workspace/single-invitation.tsx | 4 +- apps/app/constants/analytics.ts | 2 +- apps/app/constants/fetch-keys.ts | 8 + apps/app/constants/spreadsheet.ts | 4 +- .../[workspaceSlug]/me/profile/activity.tsx | 16 +- .../[workspaceSlug]/me/profile/index.tsx | 2 +- .../projects/[projectId]/settings/control.tsx | 20 +- .../projects/[projectId]/settings/members.tsx | 41 ++-- .../[workspaceSlug]/settings/members.tsx | 38 ++-- apps/app/pages/api/track-event.ts | 1 + apps/app/services/project.service.ts | 22 ++ apps/app/services/workspace.service.ts | 18 ++ apps/app/types/analytics.d.ts | 8 +- apps/app/types/cycles.d.ts | 1 + apps/app/types/projects.d.ts | 3 +- apps/app/types/users.d.ts | 9 +- apps/app/types/workspace.d.ts | 5 +- 62 files changed, 333 insertions(+), 419 deletions(-) diff --git a/apps/app/components/analytics/custom-analytics/graph/custom-tooltip.tsx b/apps/app/components/analytics/custom-analytics/graph/custom-tooltip.tsx index 573cda25c..e2698ccd2 100644 --- a/apps/app/components/analytics/custom-analytics/graph/custom-tooltip.tsx +++ b/apps/app/components/analytics/custom-analytics/graph/custom-tooltip.tsx @@ -16,15 +16,7 @@ export const CustomTooltip: React.FC = ({ datum, analytics, params }) => if (params.segment) { if (DATE_KEYS.includes(params.segment)) tooltipValue = renderMonthAndYear(datum.id); - else if (params.segment === "assignees__email") { - const assignee = analytics.extras.assignee_details.find( - (a) => a.assignees__email === datum.id - ); - - if (assignee) - tooltipValue = assignee.assignees__first_name + " " + assignee.assignees__last_name; - else tooltipValue = "No assignees"; - } else tooltipValue = datum.id; + else tooltipValue = datum.id; } else { if (DATE_KEYS.includes(params.x_axis)) tooltipValue = datum.indexValue; else tooltipValue = datum.id === "count" ? "Issue count" : "Estimate"; diff --git a/apps/app/components/analytics/custom-analytics/graph/index.tsx b/apps/app/components/analytics/custom-analytics/graph/index.tsx index 3f70dddc5..bde8c82ec 100644 --- a/apps/app/components/analytics/custom-analytics/graph/index.tsx +++ b/apps/app/components/analytics/custom-analytics/graph/index.tsx @@ -70,17 +70,17 @@ export const AnalyticsGraph: React.FC = ({ height={fullScreen ? "400px" : "300px"} margin={{ right: 20, - bottom: params.x_axis === "assignees__email" ? 50 : longestXAxisLabel.length * 5 + 20, + bottom: params.x_axis === "assignees__id" ? 50 : longestXAxisLabel.length * 5 + 20, }} axisBottom={{ tickSize: 0, tickPadding: 10, tickRotation: barGraphData.data.length > 7 ? -45 : 0, renderTick: - params.x_axis === "assignees__email" + params.x_axis === "assignees__id" ? (datum) => { const avatar = analytics.extras.assignee_details?.find( - (a) => a?.assignees__email === datum?.value + (a) => a?.assignees__display_name === datum?.value )?.assignees__avatar; if (avatar && avatar !== "") diff --git a/apps/app/components/analytics/custom-analytics/sidebar.tsx b/apps/app/components/analytics/custom-analytics/sidebar.tsx index d1a29da41..6189f325b 100644 --- a/apps/app/components/analytics/custom-analytics/sidebar.tsx +++ b/apps/app/components/analytics/custom-analytics/sidebar.tsx @@ -277,9 +277,7 @@ export const AnalyticsSidebar: React.FC = ({
Lead
- - {cycleDetails.owned_by?.first_name} {cycleDetails.owned_by?.last_name} - + {cycleDetails.owned_by?.display_name}
Start Date
@@ -305,10 +303,7 @@ export const AnalyticsSidebar: React.FC = ({
Lead
- - {moduleDetails.lead_detail?.first_name}{" "} - {moduleDetails.lead_detail?.last_name} - + {moduleDetails.lead_detail?.display_name}
Start Date
diff --git a/apps/app/components/analytics/custom-analytics/table.tsx b/apps/app/components/analytics/custom-analytics/table.tsx index 92eac5085..361f2d7f2 100644 --- a/apps/app/components/analytics/custom-analytics/table.tsx +++ b/apps/app/components/analytics/custom-analytics/table.tsx @@ -21,115 +21,96 @@ type Props = { yAxisKey: "count" | "estimate"; }; -export const AnalyticsTable: React.FC = ({ analytics, barGraphData, params, yAxisKey }) => { - const renderAssigneeName = (email: string): string => { - const assignee = analytics.extras.assignee_details.find((a) => a.assignees__email === email); - - if (!assignee) return "No assignee"; - - if (assignee.assignees__first_name !== "") - return assignee.assignees__first_name + " " + assignee.assignees__last_name; - - return email; - }; - - return ( -
-
-
- - - - - {params.segment ? ( - barGraphData.xAxisKeys.map((key) => ( - - )) - ) : ( - - )} - - - - {barGraphData.data.map((item, index) => ( - - + {params.segment ? ( + barGraphData.xAxisKeys.map((key, index) => ( + + )) + ) : ( + + )} + + ))} + +
- {ANALYTICS_X_AXIS_VALUES.find((v) => v.value === params.x_axis)?.label} - -
- {params.segment === "priority" ? ( - getPriorityIcon(key) - ) : ( - - )} - {DATE_KEYS.includes(params.segment ?? "") - ? renderMonthAndYear(key) - : params.segment === "assignees__email" - ? renderAssigneeName(key) - : key} -
-
- {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === params.y_axis)?.label} -
= ({ analytics, barGraphData, params, yAxisKey }) => ( +
+
+
+ + + + + {params.segment ? ( + barGraphData.xAxisKeys.map((key) => ( + - )) +
+ {params.segment === "priority" ? ( + getPriorityIcon(key) + ) : ( + + )} + {DATE_KEYS.includes(params.segment ?? "") ? renderMonthAndYear(key) : key} +
+ + )) + ) : ( + + )} + + + + {barGraphData.data.map((item, index) => ( + + + )} - - ))} - -
+ {ANALYTICS_X_AXIS_VALUES.find((v) => v.value === params.x_axis)?.label} + - {params.x_axis === "priority" ? ( - getPriorityIcon(`${item.name}`) - ) : ( - - )} - {params.x_axis === "assignees__email" - ? renderAssigneeName(`${item.name}`) - : addSpaceIfCamelCase(`${item.name}`)} - - {params.segment ? ( - barGraphData.xAxisKeys.map((key, index) => ( - - {item[key] ?? 0} - + {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === params.y_axis)?.label} +
+ {params.x_axis === "priority" ? ( + getPriorityIcon(`${item.name}`) ) : ( - {item[yAxisKey]}
-
+ {addSpaceIfCamelCase(`${item.name}`)} +
+ {item[key] ?? 0} + {item[yAxisKey]}
- ); -}; +
+); diff --git a/apps/app/components/analytics/scope-and-demand/leaderboard.tsx b/apps/app/components/analytics/scope-and-demand/leaderboard.tsx index 081fc6302..c22f60aa9 100644 --- a/apps/app/components/analytics/scope-and-demand/leaderboard.tsx +++ b/apps/app/components/analytics/scope-and-demand/leaderboard.tsx @@ -1,7 +1,7 @@ type Props = { users: { avatar: string | null; - email: string | null; + display_name: string | null; firstName: string; lastName: string; count: number; @@ -16,7 +16,7 @@ export const AnalyticsLeaderboard: React.FC = ({ users, title }) => (
{users.map((user) => (
@@ -25,16 +25,16 @@ export const AnalyticsLeaderboard: React.FC = ({ users, title }) => ( {user.email
) : (
- {user.firstName !== "" ? user.firstName[0] : "?"} + {user.display_name !== "" ? user?.display_name?.[0] : "?"}
)} - {user.firstName !== "" ? `${user.firstName} ${user.lastName}` : "No assignee"} + {user.display_name !== "" ? `${user.display_name}` : "No assignee"}
{user.count} diff --git a/apps/app/components/analytics/scope-and-demand/scope-and-demand.tsx b/apps/app/components/analytics/scope-and-demand/scope-and-demand.tsx index cfc315ac1..c4acf8f45 100644 --- a/apps/app/components/analytics/scope-and-demand/scope-and-demand.tsx +++ b/apps/app/components/analytics/scope-and-demand/scope-and-demand.tsx @@ -56,9 +56,9 @@ export const ScopeAndDemand: React.FC = ({ fullScreen = true }) => { ({ avatar: user?.created_by__avatar, - email: user?.created_by__email, firstName: user?.created_by__first_name, lastName: user?.created_by__last_name, + display_name: user?.created_by__display_name, count: user?.count, }))} title="Most issues created" @@ -66,9 +66,9 @@ export const ScopeAndDemand: React.FC = ({ fullScreen = true }) => { ({ avatar: user?.assignees__avatar, - email: user?.assignees__email, firstName: user?.assignees__first_name, lastName: user?.assignees__last_name, + display_name: user?.assignees__display_name, count: user?.count, }))} title="Most issues closed" diff --git a/apps/app/components/analytics/scope-and-demand/scope.tsx b/apps/app/components/analytics/scope-and-demand/scope.tsx index ac605c8f9..15b9e18de 100644 --- a/apps/app/components/analytics/scope-and-demand/scope.tsx +++ b/apps/app/components/analytics/scope-and-demand/scope.tsx @@ -16,23 +16,20 @@ export const AnalyticsScope: React.FC = ({ defaultAnalytics }) => ( {defaultAnalytics.pending_issue_user.length > 0 ? ( `#f97316`} customYAxisTickValues={defaultAnalytics.pending_issue_user.map((d) => d.count)} tooltip={(datum) => { const assignee = defaultAnalytics.pending_issue_user.find( - (a) => a.assignees__email === `${datum.indexValue}` + (a) => a.assignees__display_name === `${datum.indexValue}` ); return (
- {assignee - ? assignee.assignees__first_name + " " + assignee.assignees__last_name - : "No assignee"} - :{" "} + {assignee ? assignee.assignees__display_name : "No assignee"}:{" "} {datum.value}
diff --git a/apps/app/components/command-palette/issue/change-issue-assignee.tsx b/apps/app/components/command-palette/issue/change-issue-assignee.tsx index e272839bd..ad3d4dfdb 100644 --- a/apps/app/components/command-palette/issue/change-issue-assignee.tsx +++ b/apps/app/components/command-palette/issue/change-issue-assignee.tsx @@ -34,15 +34,12 @@ export const ChangeIssueAssignee: React.FC = ({ setIsPaletteOpen, issue, const options = members?.map(({ member }) => ({ value: member.id, - query: - (member.first_name && member.first_name !== "" ? member.first_name : member.email) + - " " + - member.last_name ?? "", + query: member.display_name, content: ( <>
- {member.first_name && member.first_name !== "" ? member.first_name : member.email} + {member.display_name}
{issue.assignees.includes(member.id) && (
diff --git a/apps/app/components/core/filters/filters-list.tsx b/apps/app/components/core/filters/filters-list.tsx index 12ae9f4c9..ffe596258 100644 --- a/apps/app/components/core/filters/filters-list.tsx +++ b/apps/app/components/core/filters/filters-list.tsx @@ -157,10 +157,10 @@ export const FiltersList: React.FC = ({ return (
- {member?.first_name} + {member?.display_name} @@ -184,7 +184,7 @@ export const FiltersList: React.FC = ({ className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1 capitalize" > - {member?.first_name} + {member?.display_name} diff --git a/apps/app/components/core/sidebar/links-list.tsx b/apps/app/components/core/sidebar/links-list.tsx index e9f8e9039..af008fec4 100644 --- a/apps/app/components/core/sidebar/links-list.tsx +++ b/apps/app/components/core/sidebar/links-list.tsx @@ -62,7 +62,7 @@ export const LinksList: React.FC = ({ links, handleDeleteLink, userAuth } by{" "} {link.created_by_detail.is_bot ? link.created_by_detail.first_name + " Bot" - : link.created_by_detail.email} + : link.created_by_detail.display_name}

diff --git a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx index 062c16eb5..a224bb773 100644 --- a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx +++ b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx @@ -133,9 +133,10 @@ export const SidebarProgressStats: React.FC = ({ avatar: assignee.avatar ?? "", first_name: assignee.first_name ?? "", last_name: assignee.last_name ?? "", + display_name: assignee.display_name ?? "", }} /> - {assignee.first_name} + {assignee.display_name}
} completed={assignee.completed_issues} diff --git a/apps/app/components/core/views/board-view/board-header.tsx b/apps/app/components/core/views/board-view/board-header.tsx index 5da92921e..6e87d1810 100644 --- a/apps/app/components/core/views/board-view/board-header.tsx +++ b/apps/app/components/core/views/board-view/board-header.tsx @@ -81,10 +81,7 @@ export const BoardHeader: React.FC = ({ break; case "created_by": const member = members?.find((member) => member.member.id === groupTitle)?.member; - title = - member?.first_name && member.first_name !== "" - ? `${member.first_name} ${member.last_name}` - : member?.email ?? ""; + title = member?.display_name ?? ""; break; } @@ -149,7 +146,9 @@ export const BoardHeader: React.FC = ({ > {getGroupIcon()}

= ({ break; case "created_by": const member = members?.find((member) => member.member.id === groupTitle)?.member; - title = - member?.first_name && member.first_name !== "" - ? `${member.first_name} ${member.last_name}` - : member?.email ?? ""; + title = member?.display_name ?? ""; break; } @@ -163,7 +160,11 @@ export const SingleList: React.FC = ({
{getGroupIcon()}
)} {selectedGroup !== null ? ( -

+

{getGroupTitle()}

) : ( diff --git a/apps/app/components/cycles/active-cycle-details.tsx b/apps/app/components/cycles/active-cycle-details.tsx index eb0cdc7d8..bec200bd9 100644 --- a/apps/app/components/cycles/active-cycle-details.tsx +++ b/apps/app/components/cycles/active-cycle-details.tsx @@ -361,14 +361,14 @@ export const ActiveCycleDetails: React.FC = () => { height={16} width={16} className="rounded-full" - alt={cycle.owned_by.first_name} + alt={cycle.owned_by.display_name} /> ) : ( - {cycle.owned_by.first_name.charAt(0)} + {cycle.owned_by.display_name.charAt(0)} )} - {cycle.owned_by.first_name} + {cycle.owned_by.display_name}

{cycle.assignees.length > 0 && ( diff --git a/apps/app/components/cycles/active-cycle-stats.tsx b/apps/app/components/cycles/active-cycle-stats.tsx index d51c5da41..e26b44803 100644 --- a/apps/app/components/cycles/active-cycle-stats.tsx +++ b/apps/app/components/cycles/active-cycle-stats.tsx @@ -88,9 +88,10 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { avatar: assignee.avatar ?? "", first_name: assignee.first_name ?? "", last_name: assignee.last_name ?? "", + display_name: assignee.display_name ?? "", }} /> - {assignee.first_name} + {assignee.display_name}
} completed={assignee.completed_issues} diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index 48edb7178..dafb1b6c2 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -450,14 +450,14 @@ export const CycleDetailsSidebar: React.FC = ({ height={12} width={12} className="rounded-full" - alt={cycle.owned_by.first_name} + alt={cycle.owned_by.display_name} /> ) : ( - {cycle.owned_by.first_name.charAt(0)} + {cycle.owned_by.display_name.charAt(0)} )} - {cycle.owned_by.first_name} + {cycle.owned_by.display_name}
diff --git a/apps/app/components/cycles/single-cycle-card.tsx b/apps/app/components/cycles/single-cycle-card.tsx index f18d0e588..8808940f1 100644 --- a/apps/app/components/cycles/single-cycle-card.tsx +++ b/apps/app/components/cycles/single-cycle-card.tsx @@ -250,14 +250,14 @@ export const SingleCycleCard: React.FC = ({ height={16} width={16} className="rounded-full" - alt={cycle.owned_by.first_name} + alt={cycle.owned_by.display_name} /> ) : ( - {cycle.owned_by.first_name.charAt(0)} + {cycle.owned_by.display_name.charAt(0)} )} - {cycle.owned_by.first_name} + {cycle.owned_by.display_name}
diff --git a/apps/app/components/cycles/single-cycle-list.tsx b/apps/app/components/cycles/single-cycle-list.tsx index 32bd18539..813264986 100644 --- a/apps/app/components/cycles/single-cycle-list.tsx +++ b/apps/app/components/cycles/single-cycle-list.tsx @@ -254,11 +254,11 @@ export const SingleCycleList: React.FC = ({ height={16} width={16} className="rounded-full" - alt={cycle.owned_by.first_name} + alt={cycle.owned_by.display_name} /> ) : ( - {cycle.owned_by.first_name.charAt(0)} + {cycle.owned_by.display_name.charAt(0)} )}
diff --git a/apps/app/components/integration/github/single-user-select.tsx b/apps/app/components/integration/github/single-user-select.tsx index 01e1f2327..13671ac89 100644 --- a/apps/app/components/integration/github/single-user-select.tsx +++ b/apps/app/components/integration/github/single-user-select.tsx @@ -44,19 +44,12 @@ export const SingleUserSelect: React.FC = ({ collaborator, index, users, ); const options = members?.map((member) => ({ - value: member.member.email, - query: - (member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email) + - " " + - member.member.last_name ?? "", + value: member.member.display_name, + query: member.member.display_name ?? "", content: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name + "(" + member.member.email + ")" - : member.member.email} + {member.member.display_name}
), })); diff --git a/apps/app/components/integration/jira/import-users.tsx b/apps/app/components/integration/jira/import-users.tsx index f73481dcd..53620f047 100644 --- a/apps/app/components/integration/jira/import-users.tsx +++ b/apps/app/components/integration/jira/import-users.tsx @@ -34,18 +34,11 @@ export const JiraImportUsers: FC = () => { const options = members?.map((member) => ({ value: member.member.email, - query: - (member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email) + - " " + - member.member.last_name ?? "", + query: member.member.display_name ?? "", content: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name + " (" + member.member.email + ")" - : member.member.email} + {member.member.display_name}
), })); diff --git a/apps/app/components/integration/single-import.tsx b/apps/app/components/integration/single-import.tsx index 56ee0adf8..b74628a83 100644 --- a/apps/app/components/integration/single-import.tsx +++ b/apps/app/components/integration/single-import.tsx @@ -42,12 +42,7 @@ export const SingleImport: React.FC = ({ service, refreshing, handleDelet
{renderShortDateWithYearFormat(service.created_at)}| - - Imported by{" "} - {service.initiated_by_detail.first_name && service.initiated_by_detail.first_name !== "" - ? service.initiated_by_detail.first_name + " " + service.initiated_by_detail.last_name - : service.initiated_by_detail.email} - + Imported by {service.initiated_by_detail.display_name}
diff --git a/apps/app/components/issues/activity.tsx b/apps/app/components/issues/activity.tsx index 54dc39613..9eb5d0c8b 100644 --- a/apps/app/components/issues/activity.tsx +++ b/apps/app/components/issues/activity.tsx @@ -122,7 +122,7 @@ export const IssueActivitySection: React.FC = ({ issueId, user }) => { activityItem.actor_detail.avatar !== "" ? ( {activityItem.actor_detail.first_name} = ({ issueId, user }) => {
- {activityItem.actor_detail.first_name.charAt(0)} + {activityItem.actor_detail.display_name.charAt(0)}
)}
@@ -150,8 +150,7 @@ export const IssueActivitySection: React.FC = ({ issueId, user }) => { ) : ( - {activityItem.actor_detail.first_name}{" "} - {activityItem.actor_detail.last_name} + {activityItem.actor_detail.display_name} )}{" "} diff --git a/apps/app/components/issues/attachment/attachments.tsx b/apps/app/components/issues/attachment/attachments.tsx index 67b5be5db..4ec598d1a 100644 --- a/apps/app/components/issues/attachment/attachments.tsx +++ b/apps/app/components/issues/attachment/attachments.tsx @@ -77,7 +77,7 @@ export const IssueAttachments = () => { person.member.id === file.updated_by)?.member - .first_name ?? "" + .display_name ?? "" } uploaded on ${renderLongDateFormat(file.updated_at)}`} > diff --git a/apps/app/components/issues/comment/comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx index 987254f3b..6805c377a 100644 --- a/apps/app/components/issues/comment/comment-card.tsx +++ b/apps/app/components/issues/comment/comment-card.tsx @@ -70,7 +70,7 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD {comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? ( {comment.actor_detail.first_name} = ({ comment, onSubmit, handleCommentD
- {comment.actor_detail.first_name.charAt(0)} + {comment.actor_detail.display_name.charAt(0)}
)} @@ -93,8 +93,9 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD
- {comment.actor_detail.first_name} - {comment.actor_detail.is_bot ? "Bot" : " " + comment.actor_detail.last_name} + {comment.actor_detail.is_bot + ? comment.actor_detail.first_name + " Bot" + : comment.actor_detail.display_name}

Commented {timeAgo(comment.created_at)} diff --git a/apps/app/components/issues/select/assignee.tsx b/apps/app/components/issues/select/assignee.tsx index 47fe07c42..278055484 100644 --- a/apps/app/components/issues/select/assignee.tsx +++ b/apps/app/components/issues/select/assignee.tsx @@ -30,20 +30,11 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on const options = members?.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 ?? "", + query: member.member.display_name ?? "", content: (

- {`${ - member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email - } ${member.member.last_name ?? ""}`} + {member.member.display_name}
), })); diff --git a/apps/app/components/issues/sidebar-select/assignee.tsx b/apps/app/components/issues/sidebar-select/assignee.tsx index f625952e3..323d201e8 100644 --- a/apps/app/components/issues/sidebar-select/assignee.tsx +++ b/apps/app/components/issues/sidebar-select/assignee.tsx @@ -41,20 +41,11 @@ export const SidebarAssigneeSelect: React.FC = ({ const options = members?.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 ?? "", + query: member.member.display_name, content: (
- {`${ - member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email - } ${member.member.last_name ?? ""}`} + {member.member.display_name}
), })); diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index 8cd3bb317..0c9e9bd27 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -47,20 +47,11 @@ export const ViewAssigneeSelect: React.FC = ({ const options = members?.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 ?? "", + query: member.member.display_name, content: (
- {`${ - member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email - } ${member.member.last_name ?? ""}`} + {member.member.display_name}
), })); @@ -71,11 +62,7 @@ export const ViewAssigneeSelect: React.FC = ({ tooltipHeading="Assignees" tooltipContent={ issue.assignee_details.length > 0 - ? issue.assignee_details - .map((assignee) => - assignee?.first_name !== "" ? assignee?.first_name : assignee?.email - ) - .join(", ") + ? issue.assignee_details.map((assignee) => assignee?.display_name).join(", ") : "No Assignee" } > diff --git a/apps/app/components/modules/select/lead.tsx b/apps/app/components/modules/select/lead.tsx index 441cb673a..e102cc21f 100644 --- a/apps/app/components/modules/select/lead.tsx +++ b/apps/app/components/modules/select/lead.tsx @@ -32,18 +32,11 @@ export const ModuleLeadSelect: React.FC = ({ value, onChange }) => { const options = members?.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 ?? "", + query: member.member.display_name, content: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} + {member.member.display_name}
), })); @@ -62,11 +55,7 @@ export const ModuleLeadSelect: React.FC = ({ value, onChange }) => { )} {selectedOption ? ( - selectedOption?.first_name && selectedOption.first_name !== "" ? ( - selectedOption?.first_name - ) : ( - selectedOption?.email - ) + selectedOption?.display_name ) : ( Lead )} diff --git a/apps/app/components/modules/select/members.tsx b/apps/app/components/modules/select/members.tsx index e7ace4060..a6e75f3a6 100644 --- a/apps/app/components/modules/select/members.tsx +++ b/apps/app/components/modules/select/members.tsx @@ -30,18 +30,11 @@ export const ModuleMembersSelect: React.FC = ({ value, onChange }) => { ); const options = members?.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 ?? "", + query: member.member.display_name, content: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} + {member.member.display_name}
), })); diff --git a/apps/app/components/modules/sidebar-select/select-lead.tsx b/apps/app/components/modules/sidebar-select/select-lead.tsx index 167d1665a..e00a5db25 100644 --- a/apps/app/components/modules/sidebar-select/select-lead.tsx +++ b/apps/app/components/modules/sidebar-select/select-lead.tsx @@ -33,18 +33,11 @@ export const SidebarLeadSelect: React.FC = ({ value, onChange }) => { const options = members?.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 ?? "", + query: member.member.display_name, content: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} + {member.member.display_name}
), })); @@ -64,11 +57,7 @@ export const SidebarLeadSelect: React.FC = ({ value, onChange }) => {
{selectedOption && } {selectedOption ? ( - selectedOption?.first_name && selectedOption.first_name !== "" ? ( - selectedOption?.first_name - ) : ( - selectedOption?.email - ) + selectedOption?.display_name ) : ( No lead )} diff --git a/apps/app/components/modules/sidebar-select/select-members.tsx b/apps/app/components/modules/sidebar-select/select-members.tsx index af530503a..185f70cec 100644 --- a/apps/app/components/modules/sidebar-select/select-members.tsx +++ b/apps/app/components/modules/sidebar-select/select-members.tsx @@ -31,18 +31,11 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => { const options = members?.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 ?? "", + query: member.member.display_name, content: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} + {member.member.display_name}
), })); diff --git a/apps/app/components/notifications/notification-card.tsx b/apps/app/components/notifications/notification-card.tsx index 3a0d2f9b8..9733a0f5b 100644 --- a/apps/app/components/notifications/notification-card.tsx +++ b/apps/app/components/notifications/notification-card.tsx @@ -78,8 +78,8 @@ export const NotificationCard: React.FC = (props) => { ) : (
- {notification.triggered_by_details.first_name?.[0] ? ( - notification.triggered_by_details.first_name?.[0]?.toUpperCase() + {notification.triggered_by_details.display_name?.[0] ? ( + notification.triggered_by_details.display_name?.[0]?.toUpperCase() ) : ( )} @@ -89,10 +89,7 @@ export const NotificationCard: React.FC = (props) => {
- - {notification.triggered_by_details.first_name}{" "} - {notification.triggered_by_details.last_name}{" "} - + {notification.triggered_by_details.display_name} {notification.data.issue_activity.field !== "comment" && notification.data.issue_activity.verb}{" "} {notification.data.issue_activity.field === "comment" diff --git a/apps/app/components/pages/single-page-detailed-item.tsx b/apps/app/components/pages/single-page-detailed-item.tsx index 928ddb935..eca4df9f1 100644 --- a/apps/app/components/pages/single-page-detailed-item.tsx +++ b/apps/app/components/pages/single-page-detailed-item.tsx @@ -162,7 +162,7 @@ export const SinglePageDetailedItem: React.FC = ({ position="top-right" tooltipContent={`Created by ${ people?.find((person) => person.member.id === page.created_by)?.member - .first_name ?? "" + .display_name ?? "" } on ${renderLongDateFormat(`${page.created_at}`)}`} > diff --git a/apps/app/components/pages/single-page-list-item.tsx b/apps/app/components/pages/single-page-list-item.tsx index e60e2d304..44a6ab6a9 100644 --- a/apps/app/components/pages/single-page-list-item.tsx +++ b/apps/app/components/pages/single-page-list-item.tsx @@ -161,7 +161,7 @@ export const SinglePageListItem: React.FC = ({ position="top-right" tooltipContent={`Created by ${ people?.find((person) => person.member.id === page.created_by)?.member - .first_name ?? "" + .display_name ?? "" } on ${renderLongDateFormat(`${page.created_at}`)}`} > diff --git a/apps/app/components/profile/overview/activity.tsx b/apps/app/components/profile/overview/activity.tsx index eb3478020..78d5e2f64 100644 --- a/apps/app/components/profile/overview/activity.tsx +++ b/apps/app/components/profile/overview/activity.tsx @@ -38,21 +38,21 @@ export const ProfileActivity = () => { {activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? ( {activity.actor_detail.first_name} ) : (
- {activity.actor_detail.first_name.charAt(0)} + {activity.actor_detail.display_name?.charAt(0)}
)}

- {activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "} + {activity.actor_detail.display_name}{" "} {activity.field ? ( diff --git a/apps/app/components/profile/profile-issues-view.tsx b/apps/app/components/profile/profile-issues-view.tsx index 401ac22d3..0b63f87a5 100644 --- a/apps/app/components/profile/profile-issues-view.tsx +++ b/apps/app/components/profile/profile-issues-view.tsx @@ -279,10 +279,10 @@ export const ProfileIssuesView = () => { dragDisabled={groupByProperty !== "priority"} emptyState={{ title: router.pathname.includes("assigned") - ? `Issues assigned to ${profileData?.user_data.first_name} ${profileData?.user_data.last_name} will appear here` + ? `Issues assigned to ${profileData?.user_data.display_name} will appear here` : router.pathname.includes("created") - ? `Issues created by ${profileData?.user_data.first_name} ${profileData?.user_data.last_name} will appear here` - : `Issues subscribed by ${profileData?.user_data.first_name} ${profileData?.user_data.last_name} will appear here`, + ? `Issues created by ${profileData?.user_data.display_name} will appear here` + : `Issues subscribed by ${profileData?.user_data.display_name} will appear here`, }} handleOnDragEnd={handleOnDragEnd} handleIssueAction={handleIssueAction} diff --git a/apps/app/components/profile/sidebar.tsx b/apps/app/components/profile/sidebar.tsx index cb2fe1f50..cf91b5150 100644 --- a/apps/app/components/profile/sidebar.tsx +++ b/apps/app/components/profile/sidebar.tsx @@ -86,29 +86,29 @@ export const ProfileSidebar = () => { userProjectsData.user_data.cover_image ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab" } - alt={userProjectsData.user_data.first_name} + alt={userProjectsData.user_data.display_name} className="h-32 w-full object-cover" />

{userProjectsData.user_data.avatar && userProjectsData.user_data.avatar !== "" ? ( {userProjectsData.user_data.first_name} ) : (
- {userProjectsData.user_data.first_name[0]} + {userProjectsData.user_data.display_name?.[0]}
)}
-

- {userProjectsData.user_data.first_name} {userProjectsData.user_data.last_name} -

-
{userProjectsData.user_data.email}
+

{userProjectsData.user_data.display_name}

+
+ {userProjectsData.user_data.display_name} +
{userDetails.map((detail) => ( diff --git a/apps/app/components/project/confirm-project-member-remove.tsx b/apps/app/components/project/confirm-project-member-remove.tsx index c6a0538a0..bfce430a7 100644 --- a/apps/app/components/project/confirm-project-member-remove.tsx +++ b/apps/app/components/project/confirm-project-member-remove.tsx @@ -67,13 +67,13 @@ const ConfirmProjectMemberRemove: React.FC = ({ isOpen, onClose, data, ha as="h3" className="text-lg font-medium leading-6 text-custom-text-100" > - Remove {data?.email}? + Remove {data?.display_name}?

Are you sure you want to remove member-{" "} - {data?.email}? They will no longer have - access to this project. This action cannot be undone. + {data?.display_name}? They will no + longer have access to this project. This action cannot be undone.

diff --git a/apps/app/components/project/create-project-modal.tsx b/apps/app/components/project/create-project-modal.tsx index 2b9c8bdc2..c48c8ec55 100644 --- a/apps/app/components/project/create-project-modal.tsx +++ b/apps/app/components/project/create-project-modal.tsx @@ -163,20 +163,11 @@ export const CreateProjectModal: React.FC = ({ isOpen, setIsOpen, user }) 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 ?? "", + query: member.member.display_name, content: (
- {`${ - member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email - } ${member.member.last_name ?? ""}`} + {member.member.display_name}
), })); @@ -376,10 +367,7 @@ export const CreateProjectModal: React.FC = ({ isOpen, setIsOpen, user }) {value ? ( <> - - {selectedMember?.member.first_name}{" "} - {selectedMember?.member.last_name} - + {selectedMember?.member.display_name} onChange(null)}> = ({ isOpen, setIsOpen, member }); const uninvitedPeople = people?.filter((person) => { - const isInvited = members?.find((member) => member.email === person.member.email); + const isInvited = members?.find((member) => member.display_name === person.member.display_name); return !isInvited; }); @@ -136,11 +136,11 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member const options = uninvitedPeople?.map((person) => ({ value: person.member.id, - query: person.member.email, + query: person.member.display_name, content: (
- {person.member.email} + {person.member.display_name}
), })); @@ -209,7 +209,10 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member people?.find((p) => p.member.id === value)?.member } /> - {people?.find((p) => p.member.id === value)?.member.email} + { + people?.find((p) => p.member.id === value)?.member + .display_name + }
) : (
Select co-worker’s email
diff --git a/apps/app/components/search-listbox/index.tsx b/apps/app/components/search-listbox/index.tsx index d563bcc6f..bd60deaab 100644 --- a/apps/app/components/search-listbox/index.tsx +++ b/apps/app/components/search-listbox/index.tsx @@ -71,9 +71,7 @@ const SearchListbox: React.FC = ({ } else return (
- {user.member.first_name && user.member.first_name !== "" - ? user.member.first_name.charAt(0) - : user.member.email.charAt(0)} + {user.member.display_name.charAt(0)}
); }; diff --git a/apps/app/components/ui/avatar.tsx b/apps/app/components/ui/avatar.tsx index 8d476a936..cc68bc18b 100644 --- a/apps/app/components/ui/avatar.tsx +++ b/apps/app/components/ui/avatar.tsx @@ -47,7 +47,7 @@ export const Avatar: React.FC = ({ {user.first_name}
) : ( @@ -59,9 +59,7 @@ export const Avatar: React.FC = ({ fontSize: fontSize, }} > - {user?.first_name && user.first_name !== "" - ? user.first_name.charAt(0) - : user?.email?.charAt(0)} + {user?.display_name?.charAt(0)}
)}
diff --git a/apps/app/components/views/select-filters.tsx b/apps/app/components/views/select-filters.tsx index 2397a18a8..c621241ca 100644 --- a/apps/app/components/views/select-filters.tsx +++ b/apps/app/components/views/select-filters.tsx @@ -127,9 +127,7 @@ export const SelectFilters: React.FC = ({ label: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} + {member.member.display_name}
), value: { @@ -149,9 +147,7 @@ export const SelectFilters: React.FC = ({ label: (
- {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} + {member.member.display_name}
), value: { diff --git a/apps/app/components/workspace/confirm-workspace-member-remove.tsx b/apps/app/components/workspace/confirm-workspace-member-remove.tsx index 2f2cae6e4..9a480c500 100644 --- a/apps/app/components/workspace/confirm-workspace-member-remove.tsx +++ b/apps/app/components/workspace/confirm-workspace-member-remove.tsx @@ -67,13 +67,13 @@ const ConfirmWorkspaceMemberRemove: React.FC = ({ isOpen, onClose, data, as="h3" className="text-lg font-medium leading-6 text-custom-text-100" > - Remove {data?.email}? + Remove {data?.display_name}?

Are you sure you want to remove member-{" "} - {data?.email}? They will no longer have - access to this workspace. This action cannot be undone. + {data?.display_name}? They will no + longer have access to this workspace. This action cannot be undone.

diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 50328ecc3..e1c92282b 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -149,7 +149,7 @@ export const WorkspaceSidebarDropdown = () => { border border-custom-sidebar-border-200 bg-custom-sidebar-background-90 shadow-lg outline-none" >
-
{user?.email}
+
{user?.display_name}
Workspace {workspaces ? (
diff --git a/apps/app/components/workspace/single-invitation.tsx b/apps/app/components/workspace/single-invitation.tsx index 833ac7ae8..02b3d72b7 100644 --- a/apps/app/components/workspace/single-invitation.tsx +++ b/apps/app/components/workspace/single-invitation.tsx @@ -41,8 +41,8 @@ const SingleInvitation: React.FC = ({

Invited by{" "} {invitation.created_by_detail - ? invitation.created_by_detail.first_name - : invitation.workspace.owner.first_name} + ? invitation.created_by_detail.display_name + : invitation.workspace.owner.display_name}

diff --git a/apps/app/constants/analytics.ts b/apps/app/constants/analytics.ts index 486a4e760..d152e211b 100644 --- a/apps/app/constants/analytics.ts +++ b/apps/app/constants/analytics.ts @@ -19,7 +19,7 @@ export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] = label: "Label", }, { - value: "assignees__email", + value: "assignees__id", label: "Assignee", }, { diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index ce6932205..1b8c77120 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -69,9 +69,13 @@ export const WORKSPACE_DETAILS = (workspaceSlug: string) => export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`; +export const WORKSPACE_MEMBERS_WITH_EMAIL = (workspaceSlug: string) => + `WORKSPACE_MEMBERS_WITH_EMAIL_${workspaceSlug.toUpperCase()}`; export const WORKSPACE_MEMBERS_ME = (workspaceSlug: string) => `WORKSPACE_MEMBERS_ME${workspaceSlug.toUpperCase()}`; export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS"; +export const WORKSPACE_INVITATION_WITH_EMAIL = (workspaceSlug: string) => + `WORKSPACE_INVITATION_WITH_EMAIL_${workspaceSlug.toUpperCase()}`; export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION"; export const LAST_ACTIVE_WORKSPACE_AND_PROJECTS = "LAST_ACTIVE_WORKSPACE_AND_PROJECTS"; @@ -90,7 +94,11 @@ export const PROJECTS_LIST = ( export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`; export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId.toUpperCase()}`; +export const PROJECT_MEMBERS_WITH_EMAIL = (workspaceSlug: string, projectId: string) => + `PROJECT_MEMBERS_WITH_EMAIL_${workspaceSlug}_${projectId.toUpperCase()}`; export const PROJECT_INVITATIONS = "PROJECT_INVITATIONS"; +export const PROJECT_INVITATIONS_WITH_EMAIL = (workspaceSlug: string, projectId: string) => + `PROJECT_INVITATIONS_WITH_EMAIL_${workspaceSlug}_${projectId.toUpperCase()}`; export const PROJECT_ISSUES_LIST = (workspaceSlug: string, projectId: string) => `PROJECT_ISSUES_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}`; diff --git a/apps/app/constants/spreadsheet.ts b/apps/app/constants/spreadsheet.ts index 371fb50a3..0dcab7220 100644 --- a/apps/app/constants/spreadsheet.ts +++ b/apps/app/constants/spreadsheet.ts @@ -32,8 +32,8 @@ export const SPREADSHEET_COLUMN = [ colName: "Assignees", colSize: "128px", icon: UserGroupIcon, - ascendingOrder: "assignees__first_name", - descendingOrder: "-assignees__first_name", + ascendingOrder: "assignees__id", + descendingOrder: "-assignees__id", }, { propertyName: "labels", diff --git a/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx b/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx index 1b0dc005d..1b04388e6 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx @@ -73,7 +73,7 @@ const ProfileActivity = () => { activityItem.actor_detail.avatar !== "" ? ( {activityItem.actor_detail.first_name} {
- {activityItem.actor_detail.first_name.charAt(0)} + {activityItem.actor_detail.display_name?.charAt(0)}
)} @@ -96,10 +96,9 @@ const ProfileActivity = () => {
- {activityItem.actor_detail.first_name} {activityItem.actor_detail.is_bot - ? "Bot" - : " " + activityItem.actor_detail.last_name} + ? activityItem.actor_detail.first_name + " Bot" + : activityItem.actor_detail.display_name}

Commented {timeAgo(activityItem.created_at)} @@ -176,7 +175,7 @@ const ProfileActivity = () => { activityItem.actor_detail.avatar !== "" ? ( {activityItem.actor_detail.first_name} {

- {activityItem.actor_detail.first_name.charAt(0)} + {activityItem.actor_detail.display_name?.charAt(0)}
)}
@@ -206,8 +205,7 @@ const ProfileActivity = () => { href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`} > - {activityItem.actor_detail.first_name}{" "} - {activityItem.actor_detail.last_name} + {activityItem.actor_detail.display_name} )}{" "} diff --git a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx index 3fb73c40d..2ae984921 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx @@ -176,7 +176,7 @@ const Profile: NextPage = () => { src={watch("avatar")} className="absolute top-0 left-0 h-full w-full object-cover rounded-md" onClick={() => setIsImageUploadModalOpen(true)} - alt={myProfile.first_name} + alt={myProfile.display_name} />
)} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx index 9e8d437e8..12ef350aa 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx @@ -131,7 +131,7 @@ const ControlSettings: NextPage = () => { {...field} label={ people?.find((person) => person.member.id === field.value)?.member - .first_name ?? Select lead + .display_name ?? Select lead } width="w-full" input @@ -153,14 +153,10 @@ const ControlSettings: NextPage = () => {
) : (
- {person.member.first_name && person.member.first_name !== "" - ? person.member.first_name.charAt(0) - : person.member.email.charAt(0)} + {person.member.display_name?.charAt(0)}
)} - {person.member.first_name !== "" - ? person.member.first_name - : person.member.email} + {person.member.display_name}
))} @@ -190,7 +186,7 @@ const ControlSettings: NextPage = () => { p.member.id === field.value)?.member.first_name ?? ( + people?.find((p) => p.member.id === field.value)?.member.display_name ?? ( Select default assignee ) } @@ -214,14 +210,10 @@ const ControlSettings: NextPage = () => {
) : (
- {person.member.first_name && person.member.first_name !== "" - ? person.member.first_name.charAt(0) - : person.member.email.charAt(0)} + {person.member.display_name?.charAt(0)}
)} - {person.member.first_name !== "" - ? person.member.first_name - : person.member.email} + {person.member.display_name}
))} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index d828b3912..88301cc61 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -26,7 +26,11 @@ import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types import type { NextPage } from "next"; // fetch-keys -import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys"; +import { + PROJECT_INVITATIONS_WITH_EMAIL, + PROJECT_MEMBERS_WITH_EMAIL, + WORKSPACE_DETAILS, +} from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; // helper @@ -51,16 +55,21 @@ const MembersSettings: NextPage = () => { ); const { data: projectMembers, mutate: mutateMembers } = useSWR( - workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, workspaceSlug && projectId - ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) + ? PROJECT_MEMBERS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString()) + : null, + workspaceSlug && projectId + ? () => projectService.projectMembersWithEmail(workspaceSlug as string, projectId as string) : null ); const { data: projectInvitations, mutate: mutateInvitations } = useSWR( - workspaceSlug && projectId ? PROJECT_INVITATIONS : null, workspaceSlug && projectId - ? () => projectService.projectInvitations(workspaceSlug as string, projectId as string) + ? PROJECT_INVITATIONS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString()) + : null, + workspaceSlug && projectId + ? () => + projectService.projectInvitationsWithEmail(workspaceSlug as string, projectId as string) : null ); @@ -71,7 +80,7 @@ const MembersSettings: NextPage = () => { avatar: item.member?.avatar, first_name: item.member?.first_name, last_name: item.member?.last_name, - email: item.member?.email, + display_name: item.member?.display_name, role: item.role, status: true, member: true, @@ -82,7 +91,7 @@ const MembersSettings: NextPage = () => { avatar: item.avatar ?? "", first_name: item.first_name ?? item.email, last_name: item.last_name ?? "", - email: item.email, + display_name: item.email, role: item.role, status: item.accepted, member: false, @@ -181,28 +190,24 @@ const MembersSettings: NextPage = () => { {member.avatar && member.avatar !== "" ? ( {member.first_name} - ) : member.first_name !== "" ? ( - member.first_name.charAt(0) ) : ( - member.email.charAt(0) + member.display_name.charAt(0) )}
{member.member ? ( - - {member.first_name} {member.last_name} - + {member.display_name} ) : ( -

- {member.first_name} {member.last_name} -

+

{member.display_name}

)} -

{member.email}

+

+ {member.display_name} +

diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index 0fc2d5fd9..b348d5f7f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -24,7 +24,11 @@ import { PlusIcon } from "@heroicons/react/24/outline"; // types import type { NextPage } from "next"; // fetch-keys -import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys"; +import { + WORKSPACE_DETAILS, + WORKSPACE_INVITATION_WITH_EMAIL, + WORKSPACE_MEMBERS_WITH_EMAIL, +} from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; // helper @@ -48,13 +52,17 @@ const MembersSettings: NextPage = () => { ); const { data: workspaceMembers, mutate: mutateMembers } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug.toString()) : null, - workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug.toString()) : null + workspaceSlug ? WORKSPACE_MEMBERS_WITH_EMAIL(workspaceSlug.toString()) : null, + workspaceSlug + ? () => workspaceService.workspaceMembersWithEmail(workspaceSlug.toString()) + : null ); const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR( - workspaceSlug ? WORKSPACE_INVITATIONS : null, - workspaceSlug ? () => workspaceService.workspaceInvitations(workspaceSlug.toString()) : null + workspaceSlug ? WORKSPACE_INVITATION_WITH_EMAIL(workspaceSlug.toString()) : null, + workspaceSlug + ? () => workspaceService.workspaceInvitationsWithEmail(workspaceSlug.toString()) + : null ); const members = [ @@ -65,6 +73,7 @@ const MembersSettings: NextPage = () => { first_name: item.member?.first_name, last_name: item.member?.last_name, email: item.member?.email, + display_name: item.member?.display_name, role: item.role, status: true, member: true, @@ -77,6 +86,7 @@ const MembersSettings: NextPage = () => { first_name: item.email, last_name: "", email: item.email, + display_name: item.email, role: item.role, status: item.accepted, member: false, @@ -199,27 +209,23 @@ const MembersSettings: NextPage = () => { {member.first_name} - ) : member.first_name !== "" ? ( - member.first_name.charAt(0) ) : ( - member.email.charAt(0) + (member.display_name || member.email).charAt(0) )}
{member.member ? ( - - {member.first_name} {member.last_name} - + {member.display_name || member.email} ) : ( -

- {member.first_name} {member.last_name} -

+

{member.display_name}

)} -

{member.email}

+

+ {member.display_name || member.email} +

diff --git a/apps/app/pages/api/track-event.ts b/apps/app/pages/api/track-event.ts index c362f68fd..d6158cf9a 100644 --- a/apps/app/pages/api/track-event.ts +++ b/apps/app/pages/api/track-event.ts @@ -26,6 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) email: user.email, first_name: user.first_name, last_name: user.last_name, + display_name: user?.display_name, }) .then(() => { jitsu.track(eventName, { diff --git a/apps/app/services/project.service.ts b/apps/app/services/project.service.ts index d25bcd9a6..961333bee 100644 --- a/apps/app/services/project.service.ts +++ b/apps/app/services/project.service.ts @@ -151,6 +151,17 @@ class ProjectServices extends APIService { } async projectMembers(workspaceSlug: string, projectId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async projectMembersWithEmail( + workspaceSlug: string, + projectId: string + ): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`) .then((response) => response?.data) .catch((error) => { @@ -219,6 +230,17 @@ class ProjectServices extends APIService { }); } + async projectInvitationsWithEmail( + workspaceSlug: string, + projectId: string + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + async updateProjectInvitation( workspaceSlug: string, projectId: string, diff --git a/apps/app/services/workspace.service.ts b/apps/app/services/workspace.service.ts index c9824c26a..8097253e6 100644 --- a/apps/app/services/workspace.service.ts +++ b/apps/app/services/workspace.service.ts @@ -155,6 +155,14 @@ class WorkspaceService extends APIService { } async workspaceMembers(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async workspaceMembersWithEmail(workspaceSlug: string): Promise { return this.get(`/api/workspaces/${workspaceSlug}/members/`) .then((response) => response?.data) .catch((error) => { @@ -209,6 +217,16 @@ class WorkspaceService extends APIService { }); } + async workspaceInvitationsWithEmail( + workspaceSlug: string + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/invitations/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + async getWorkspaceInvitation(invitationId: string): Promise { return this.get(`/api/users/me/invitations/${invitationId}/`, { headers: {} }) .then((response) => response?.data) diff --git a/apps/app/types/analytics.d.ts b/apps/app/types/analytics.d.ts index 67bc0cd10..34310a631 100644 --- a/apps/app/types/analytics.d.ts +++ b/apps/app/types/analytics.d.ts @@ -4,8 +4,8 @@ export interface IAnalyticsResponse { extras: { colors: IAnalyticsExtra[]; assignee_details: { + assignees__display_name: string | null; assignees__avatar: string | null; - assignees__email: string; assignees__first_name: string; assignees__last_name: string; }[]; @@ -30,7 +30,7 @@ export type TXAxisValues = | "state__name" | "state__group" | "labels__name" - | "assignees__email" + | "assignees__id" | "estimate_point" | "issue_cycle__cycle__name" | "issue_module__module__name" @@ -65,9 +65,9 @@ export interface IExportAnalyticsFormData { export interface IDefaultAnalyticsUser { assignees__avatar: string | null; - assignees__email: string | null; assignees__first_name: string; assignees__last_name: string; + assignees__display_name: string; count: number; } @@ -76,9 +76,9 @@ export interface IDefaultAnalyticsResponse { most_issue_closed_user: IDefaultAnalyticsUser[]; most_issue_created_user: { created_by__avatar: string | null; - created_by__email: string | null; created_by__first_name: string; created_by__last_name: string; + created_by__display_name: string; count: number; }[]; open_estimate_sum: number; diff --git a/apps/app/types/cycles.d.ts b/apps/app/types/cycles.d.ts index 878e9c060..df358b7a9 100644 --- a/apps/app/types/cycles.d.ts +++ b/apps/app/types/cycles.d.ts @@ -49,6 +49,7 @@ export type TAssigneesDistribution = { completed_issues: number; first_name: string | null; last_name: string | null; + display_name: string | null; pending_issues: number; total_issues: number; }; diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 2274e3d22..df71adb31 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -3,6 +3,7 @@ import type { IUserLite, IWorkspace, IWorkspaceLite, + IUserMemberLite, TIssueGroupByOptions, TIssueOrderByOptions, TIssueViewOptions, @@ -78,7 +79,7 @@ type ProjectPreferences = { export interface IProjectMember { id: string; - member: IUserLite; + member: IUserMemberLite; project: IProjectLite; workspace: IWorkspaceLite; comment: string; diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index 5b174cc5a..68f0b0c78 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -15,6 +15,7 @@ export interface IUser { created_location: readonly string; date_joined: readonly Date; email: string; + display_name: string; first_name: string; id: readonly string; is_email_verified: boolean; @@ -53,13 +54,17 @@ export interface ICurrentUserResponse extends IUser { export interface IUserLite { avatar: string; created_at: Date; - email: string; + display_name: string; first_name: string; readonly id: string; is_bot: boolean; last_name: string; } +export interface IUserMemberLite extends IUserLite { + email: string; +} + export interface IUserActivity { created_date: string; activity_count: number; @@ -141,7 +146,7 @@ export interface IUserProfileProjectSegregation { avatar: string; cover_image: string | null; date_joined: Date; - email: string; + display_name: string; first_name: string; last_name: string; user_timezone: string; diff --git a/apps/app/types/workspace.d.ts b/apps/app/types/workspace.d.ts index 5546a680c..c26ce5362 100644 --- a/apps/app/types/workspace.d.ts +++ b/apps/app/types/workspace.d.ts @@ -2,7 +2,7 @@ import type { IIssueFilterOptions, IProjectMember, IUser, - IUserLite, + IUserMemberLite, TIssueGroupByOptions, TIssueOrderByOptions, TIssueViewOptions, @@ -73,9 +73,8 @@ export interface IWorkspaceViewProps { export interface IWorkspaceMember { readonly id: string; - user: IUserLite; workspace: IWorkspace; - member: IUserLite; + member: IUserMemberLite; role: 5 | 10 | 15 | 20; company_role: string | null; view_props: IWorkspaceViewProps; From 88e5a05253ff87fe1cae9bfde8d46e633bafffd0 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:15:25 +0530 Subject: [PATCH 17/82] chore: subscribed by me tab on my issues page (#1800) * chore: add subscribed by me tab in my issues * chore: update tab titles * fix: build error --- .../issues/my-issues/my-issues-view.tsx | 8 +++++-- apps/app/constants/fetch-keys.ts | 10 ++++---- apps/app/contexts/issue-view.context.tsx | 1 + apps/app/contexts/profile-issues-context.tsx | 6 ++--- .../hooks/my-issues/use-my-issues-filter.tsx | 1 + apps/app/hooks/my-issues/use-my-issues.tsx | 1 + apps/app/hooks/use-profile-issues.tsx | 4 ++-- .../pages/[workspaceSlug]/me/my-issues.tsx | 23 +++++++++++++++---- apps/app/types/issues.d.ts | 1 + 9 files changed, 38 insertions(+), 17 deletions(-) diff --git a/apps/app/components/issues/my-issues/my-issues-view.tsx b/apps/app/components/issues/my-issues/my-issues-view.tsx index a30c7092c..a6fdc81b4 100644 --- a/apps/app/components/issues/my-issues/my-issues-view.tsx +++ b/apps/app/components/issues/my-issues/my-issues-view.tsx @@ -200,7 +200,7 @@ export const MyIssuesView: React.FC = ({ [makeIssueCopy, handleEditIssue, handleDeleteIssue] ); - const filtersToDisplay = { ...filters, assignees: null, created_by: null }; + const filtersToDisplay = { ...filters, assignees: null, created_by: null, subscriber: null }; const nullFilters = Object.keys(filtersToDisplay).filter( (key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null @@ -264,7 +264,11 @@ export const MyIssuesView: React.FC = ({ disableUserActions={disableUserActions} dragDisabled={groupBy !== "priority"} emptyState={{ - title: "You don't have any issue assigned to you yet", + title: filters.assignees + ? "You don't have any issue assigned to you yet" + : filters.created_by + ? "You have not created any issue yet." + : "You have not subscribed to any issue yet.", description: "Keep track of your work in a single place.", primaryButton: { icon: , diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index 1b8c77120..b6d164068 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -38,11 +38,12 @@ const inboxParamsToKey = (params: any) => { }; const myIssuesParamsToKey = (params: any) => { - const { assignees, created_by, labels, priority, state_group, target_date } = params; + const { assignees, created_by, labels, priority, state_group, subscriber, target_date } = params; let assigneesKey = assignees ? assignees.split(",") : []; let createdByKey = created_by ? created_by.split(",") : []; let stateGroupKey = state_group ? state_group.split(",") : []; + let subscriberKey = subscriber ? subscriber.split(",") : []; let priorityKey = priority ? priority.split(",") : []; let labelsKey = labels ? labels.split(",") : []; const targetDateKey = target_date ?? ""; @@ -54,10 +55,11 @@ const myIssuesParamsToKey = (params: any) => { assigneesKey = assigneesKey.sort().join("_"); createdByKey = createdByKey.sort().join("_"); stateGroupKey = stateGroupKey.sort().join("_"); + subscriberKey = subscriberKey.sort().join("_"); priorityKey = priorityKey.sort().join("_"); labelsKey = labelsKey.sort().join("_"); - return `${assigneesKey}_${createdByKey}_${stateGroupKey}_${priorityKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${targetDateKey}`; + return `${assigneesKey}_${createdByKey}_${stateGroupKey}_${subscriberKey}_${priorityKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${targetDateKey}`; }; export const CURRENT_USER = "CURRENT_USER"; @@ -317,9 +319,7 @@ export const USER_PROFILE_PROJECT_SEGREGATION = (workspaceSlug: string, userId: export const USER_PROFILE_ISSUES = (workspaceSlug: string, userId: string, params: any) => { const paramsKey = myIssuesParamsToKey(params); - const subscriberKey = params.subscriber ? params.subscriber.toUpperCase() : "NULL"; - - return `USER_PROFILE_ISSUES_${workspaceSlug.toUpperCase()}_${userId.toUpperCase()}_${paramsKey}_${subscriberKey}`; + return `USER_PROFILE_ISSUES_${workspaceSlug.toUpperCase()}_${userId.toUpperCase()}_${paramsKey}`; }; // reactions diff --git a/apps/app/contexts/issue-view.context.tsx b/apps/app/contexts/issue-view.context.tsx index 09aa9e88c..aa59f4998 100644 --- a/apps/app/contexts/issue-view.context.tsx +++ b/apps/app/contexts/issue-view.context.tsx @@ -92,6 +92,7 @@ export const initialState: StateType = { labels: null, state: null, state_group: null, + subscriber: null, created_by: null, target_date: null, }, diff --git a/apps/app/contexts/profile-issues-context.tsx b/apps/app/contexts/profile-issues-context.tsx index 7d1f04b44..59675eb68 100644 --- a/apps/app/contexts/profile-issues-context.tsx +++ b/apps/app/contexts/profile-issues-context.tsx @@ -19,7 +19,7 @@ type IssueViewProps = { orderBy: TIssueOrderByOptions; showEmptyGroups: boolean; showSubIssues: boolean; - filters: IIssueFilterOptions & { subscriber: string | null }; + filters: IIssueFilterOptions; properties: Properties; }; @@ -41,7 +41,7 @@ type ContextType = IssueViewProps & { setOrderBy: (property: TIssueOrderByOptions) => void; setShowEmptyGroups: (property: boolean) => void; setShowSubIssues: (value: boolean) => void; - setFilters: (filters: Partial) => void; + setFilters: (filters: Partial) => void; setProperties: (key: keyof Properties) => void; setIssueView: (property: TIssueViewOptions) => void; }; @@ -52,7 +52,7 @@ type StateType = { orderBy: TIssueOrderByOptions; showEmptyGroups: boolean; showSubIssues: boolean; - filters: IIssueFilterOptions & { subscriber: string | null }; + filters: IIssueFilterOptions; properties: Properties; }; type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; diff --git a/apps/app/hooks/my-issues/use-my-issues-filter.tsx b/apps/app/hooks/my-issues/use-my-issues-filter.tsx index 4f45d6b80..fff702f1f 100644 --- a/apps/app/hooks/my-issues/use-my-issues-filter.tsx +++ b/apps/app/hooks/my-issues/use-my-issues-filter.tsx @@ -25,6 +25,7 @@ const initialValues: IWorkspaceViewProps = { labels: null, priority: null, state_group: null, + subscriber: null, target_date: null, type: null, }, diff --git a/apps/app/hooks/my-issues/use-my-issues.tsx b/apps/app/hooks/my-issues/use-my-issues.tsx index f9063b4de..6d39e548f 100644 --- a/apps/app/hooks/my-issues/use-my-issues.tsx +++ b/apps/app/hooks/my-issues/use-my-issues.tsx @@ -26,6 +26,7 @@ const useMyIssues = (workspaceSlug: string | undefined) => { order_by: orderBy, priority: filters?.priority ? filters?.priority.join(",") : undefined, state_group: filters?.state_group ? filters?.state_group.join(",") : undefined, + subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined, target_date: filters?.target_date ? filters?.target_date.join(",") : undefined, type: filters?.type ? filters?.type : undefined, }; diff --git a/apps/app/hooks/use-profile-issues.tsx b/apps/app/hooks/use-profile-issues.tsx index 850886462..7e7f24372 100644 --- a/apps/app/hooks/use-profile-issues.tsx +++ b/apps/app/hooks/use-profile-issues.tsx @@ -46,7 +46,7 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un state_group: filters?.state_group ? filters?.state_group.join(",") : undefined, target_date: filters?.target_date ? filters?.target_date.join(",") : undefined, type: filters?.type ? filters?.type : undefined, - subscriber: filters?.subscriber ? filters?.subscriber : undefined, + subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined, }; const { data: userProfileIssues, mutate: mutateProfileIssues } = useSWR( @@ -93,7 +93,7 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un } if (router.pathname.includes("subscribed") && filters.subscriber === null) { - setFilters({ subscriber: userId }); + setFilters({ subscriber: [userId] }); return; } }, [filters, router, setFilters, userId]); diff --git a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx index cf3ed36c2..257172781 100644 --- a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx +++ b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx @@ -22,7 +22,6 @@ const MyIssuesPage: NextPage = () => { const router = useRouter(); const { workspaceSlug } = router.query; - const { projects } = useProjects(); const { user } = useUser(); const { filters, setFilters } = useMyIssuesFilters(workspaceSlug?.toString()); @@ -30,23 +29,37 @@ const MyIssuesPage: NextPage = () => { const tabsList = [ { key: "assigned", - label: "Assigned to me", + label: "Assigned", selected: (filters?.assignees ?? []).length > 0, onClick: () => { setFilters({ assignees: [user?.id ?? ""], created_by: null, + subscriber: null, }); }, }, { key: "created", - label: "Created by me", + label: "Created", selected: (filters?.created_by ?? []).length > 0, onClick: () => { setFilters({ - created_by: [user?.id ?? ""], assignees: null, + created_by: [user?.id ?? ""], + subscriber: null, + }); + }, + }, + { + key: "subscribed", + label: "Subscribed", + selected: (filters?.subscriber ?? []).length > 0, + onClick: () => { + setFilters({ + assignees: null, + created_by: null, + subscriber: [user?.id ?? ""], }); }, }, @@ -55,7 +68,7 @@ const MyIssuesPage: NextPage = () => { useEffect(() => { if (!filters || !user) return; - if (!filters.assignees && !filters.created_by) { + if (!filters.assignees && !filters.created_by && !filters.subscriber) { setFilters({ assignees: [user.id], }); diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index 94264a22c..71389e088 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -229,6 +229,7 @@ export interface IIssueFilterOptions { target_date: string[] | null; state: string[] | null; state_group: TStateGroups[] | null; + subscriber: string[] | null; labels: string[] | null; priority: string[] | null; created_by: string[] | null; From 5f1209f1dbaec55e36f5d3320e65b4c3cfc6168a Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:13:26 +0530 Subject: [PATCH 18/82] chore: empty state for multi-level dropdown (#1802) * fix :label filter should show something if there is no label #1779 (#1795) * style: children empty state --------- Co-authored-by: Pankaj Chotaliya <34762752+pankajvc@users.noreply.github.com> --- .../components/ui/multi-level-dropdown.tsx | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index 9a4ebdbe4..cfdaa72d5 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -66,7 +66,7 @@ export const MultiLevelDropdown: React.FC = ({ > {options.map((option) => (
@@ -107,7 +107,7 @@ export const MultiLevelDropdown: React.FC = ({ {option.hasChildren && option.id === openChildFor && (
= ({ > {option.children ? (
- {option.children.map((child) => { - if (child.element) return child.element; - else - return ( - - ); - })} + {option.children.length === 0 ? ( +

+ No {option.label} found +

//if no children found, show this message. + ) : ( + option.children.map((child) => { + if (child.element) return child.element; + else + return ( + + ); + }) + )}
) : ( From 4fcd081d279fa92853c88f1ba531cd77bb89b4b6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:17:32 +0530 Subject: [PATCH 19/82] chore: select start date option for issue (#1813) --- apps/app/components/core/activity.tsx | 34 ++++++++ .../core/views/board-view/single-issue.tsx | 9 +++ .../core/views/calendar-view/single-issue.tsx | 10 ++- .../core/views/list-view/single-issue.tsx | 9 +++ apps/app/components/issues/form.tsx | 34 +++++++- apps/app/components/issues/modal.tsx | 1 + apps/app/components/issues/select/date.tsx | 11 ++- apps/app/components/issues/sidebar.tsx | 39 +++++++++ .../issues/view-select/due-date.tsx | 8 +- .../components/issues/view-select/index.ts | 5 +- .../issues/view-select/start-date.tsx | 80 +++++++++++++++++++ apps/app/components/ui/datepicker.tsx | 3 + apps/app/hooks/use-issue-properties.tsx | 2 + .../projects/[projectId]/issues/[issueId].tsx | 11 +-- apps/app/types/issues.d.ts | 16 +--- apps/app/types/workspace.d.ts | 1 + 16 files changed, 243 insertions(+), 30 deletions(-) create mode 100644 apps/app/components/issues/view-select/start-date.tsx diff --git a/apps/app/components/core/activity.tsx b/apps/app/components/core/activity.tsx index f7622baa6..833f4fd16 100644 --- a/apps/app/components/core/activity.tsx +++ b/apps/app/components/core/activity.tsx @@ -428,6 +428,40 @@ const activityDetails: { ), icon: