From 7767be2e216e21745100f67ec0197574fcfbe9cc Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Mon, 8 Jul 2024 18:52:30 +0530 Subject: [PATCH] [WEB-1889] fix: handled tapping on a notification in Notifications from mobile in inbox issue and issue peek overview component (#5074) * fix: handled tapping on a notification in Notifications from mobile in inbox issue and issue peekoverview component * fix: code cleanup * fix: code cleanup on workspace notification store * fix: updated selected notification on workspace notification store --- .../types/src/workspace-notifications.d.ts | 2 +- .../(projects)/notifications/layout.tsx | 4 +- .../(projects)/notifications/page.tsx | 17 ++- .../inbox/content/inbox-issue-header.tsx | 8 ++ web/core/components/inbox/content/root.tsx | 3 + .../components/issues/peek-overview/root.tsx | 4 +- .../components/issues/peek-overview/view.tsx | 11 +- .../sidebar/notification-card/item.tsx | 27 +---- .../workspace-notifications/sidebar/root.tsx | 111 ++++++++++-------- .../workspace-notifications.store.ts | 43 ++++--- 10 files changed, 131 insertions(+), 99 deletions(-) diff --git a/packages/types/src/workspace-notifications.d.ts b/packages/types/src/workspace-notifications.d.ts index 485af600a1..718387f326 100644 --- a/packages/types/src/workspace-notifications.d.ts +++ b/packages/types/src/workspace-notifications.d.ts @@ -90,7 +90,7 @@ export type TUnreadNotificationsCount = { mention_unread_notifications_count: number; }; -export type TCurrentSelectedNotification = { +export type TNotificationLite = { workspace_slug: string | undefined; project_id: string | undefined; notification_id: string | undefined; diff --git a/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx b/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx index 49303d3614..b8b80b4d27 100644 --- a/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx @@ -6,9 +6,7 @@ import { NotificationsSidebar } from "@/components/workspace-notifications"; export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { return (
-
- -
+
{children}
); diff --git a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx index 0e29a84d1b..8361b15f0b 100644 --- a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx @@ -15,13 +15,20 @@ import { useUser, useWorkspace, useWorkspaceNotifications } from "@/hooks/store" const WorkspaceDashboardPage = observer(() => { // hooks const { currentWorkspace } = useWorkspace(); - const { currentSelectedNotification, notificationIdsByWorkspaceId, getNotifications } = useWorkspaceNotifications(); + const { + currentSelectedNotificationId, + setCurrentSelectedNotificationId, + notificationLiteByNotificationId, + notificationIdsByWorkspaceId, + getNotifications, + } = useWorkspaceNotifications(); const { membership: { fetchUserProjectInfo }, } = useUser(); // derived values const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Notifications` : undefined; - const { workspace_slug, project_id, issue_id, is_inbox_issue } = currentSelectedNotification; + const { workspace_slug, project_id, issue_id, is_inbox_issue } = + notificationLiteByNotificationId(currentSelectedNotificationId); // fetch workspace notifications const notificationMutation = @@ -65,11 +72,15 @@ const WorkspaceDashboardPage = observer(() => { projectId={project_id} inboxIssueId={issue_id} isNotificationEmbed + embedRemoveCurrentNotification={() => setCurrentSelectedNotificationId(undefined)} /> )} ) : ( - + setCurrentSelectedNotificationId(undefined)} + /> )} diff --git a/web/core/components/inbox/content/inbox-issue-header.tsx b/web/core/components/inbox/content/inbox-issue-header.tsx index a7302005af..a7aa569203 100644 --- a/web/core/components/inbox/content/inbox-issue-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-header.tsx @@ -12,6 +12,7 @@ import { FileStack, Link, Trash2, + MoveRight, } from "lucide-react"; import { Button, ControlLink, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; // components @@ -45,6 +46,7 @@ type TInboxIssueActionsHeader = { isMobileSidebar: boolean; setIsMobileSidebar: (value: boolean) => void; isNotificationEmbed: boolean; + embedRemoveCurrentNotification?: () => void; }; export const InboxIssueActionsHeader: FC = observer((props) => { @@ -56,6 +58,7 @@ export const InboxIssueActionsHeader: FC = observer((p isMobileSidebar, setIsMobileSidebar, isNotificationEmbed = false, + embedRemoveCurrentNotification, } = props; // states const [isSnoozeDateModalOpen, setIsSnoozeDateModalOpen] = useState(false); @@ -240,6 +243,11 @@ export const InboxIssueActionsHeader: FC = observer((p
+ {isNotificationEmbed && ( + + )} {issue?.project_id && issue.sequence_id && (

{getProjectById(issue.project_id)?.identifier}-{issue.sequence_id} diff --git a/web/core/components/inbox/content/root.tsx b/web/core/components/inbox/content/root.tsx index 406568f485..c87afb9c1d 100644 --- a/web/core/components/inbox/content/root.tsx +++ b/web/core/components/inbox/content/root.tsx @@ -16,6 +16,7 @@ type TInboxContentRoot = { isMobileSidebar: boolean; setIsMobileSidebar: (value: boolean) => void; isNotificationEmbed?: boolean; + embedRemoveCurrentNotification?: () => void; }; export const InboxContentRoot: FC = observer((props) => { @@ -26,6 +27,7 @@ export const InboxContentRoot: FC = observer((props) => { isMobileSidebar, setIsMobileSidebar, isNotificationEmbed = false, + embedRemoveCurrentNotification, } = props; /// router const router = useAppRouter(); @@ -78,6 +80,7 @@ export const InboxContentRoot: FC = observer((props) => { inboxIssue={inboxIssue} isSubmitting={isSubmitting} isNotificationEmbed={isNotificationEmbed || false} + embedRemoveCurrentNotification={embedRemoveCurrentNotification} />

diff --git a/web/core/components/issues/peek-overview/root.tsx b/web/core/components/issues/peek-overview/root.tsx index 7211245520..597f3179af 100644 --- a/web/core/components/issues/peek-overview/root.tsx +++ b/web/core/components/issues/peek-overview/root.tsx @@ -17,6 +17,7 @@ import { useIssuesStore } from "@/hooks/use-issue-layout-store"; interface IIssuePeekOverview { embedIssue?: boolean; + embedRemoveCurrentNotification?: () => void; is_archived?: boolean; is_draft?: boolean; } @@ -45,7 +46,7 @@ export type TIssuePeekOperations = { }; export const IssuePeekOverview: FC = observer((props) => { - const { embedIssue = false, is_archived = false, is_draft = false } = props; + const { embedIssue = false, embedRemoveCurrentNotification, is_archived = false, is_draft = false } = props; // router const pathname = usePathname(); const { @@ -362,6 +363,7 @@ export const IssuePeekOverview: FC = observer((props) => { is_archived={is_archived} disabled={!isEditable} embedIssue={embedIssue} + embedRemoveCurrentNotification={embedRemoveCurrentNotification} issueOperations={issueOperations} /> ); diff --git a/web/core/components/issues/peek-overview/view.tsx b/web/core/components/issues/peek-overview/view.tsx index c92c431b19..ae94722a7c 100644 --- a/web/core/components/issues/peek-overview/view.tsx +++ b/web/core/components/issues/peek-overview/view.tsx @@ -30,6 +30,7 @@ interface IIssueView { is_archived: boolean; disabled?: boolean; embedIssue?: boolean; + embedRemoveCurrentNotification?: () => void; issueOperations: TIssueOperations; } @@ -43,6 +44,7 @@ export const IssueView: FC = observer((props) => { is_archived, disabled = false, embedIssue = false, + embedRemoveCurrentNotification, issueOperations, } = props; // states @@ -64,13 +66,16 @@ export const IssueView: FC = observer((props) => { // remove peek id const removeRoutePeekId = () => { setPeekIssue(undefined); + if (embedIssue) embedRemoveCurrentNotification && embedRemoveCurrentNotification(); }; usePeekOverviewOutsideClickDetector( issuePeekOverviewRef, () => { - if (!isAnyModalOpen) { - removeRoutePeekId(); + if (!embedIssue) { + if (!isAnyModalOpen) { + removeRoutePeekId(); + } } }, issueId @@ -86,7 +91,7 @@ export const IssueView: FC = observer((props) => { } }; - useKeypress("Escape", handleKeyDown); + useKeypress("Escape", () => !embedIssue && handleKeyDown()); const handleRestore = async () => { if (!issueOperations.restore) return; diff --git a/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx b/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx index 19dc0b2064..25fc4bfefb 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx @@ -3,7 +3,6 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; import { Clock } from "lucide-react"; -import { TCurrentSelectedNotification } from "@plane/types"; import { Avatar } from "@plane/ui"; // components import { NotificationOption } from "@/components/workspace-notifications"; @@ -23,7 +22,7 @@ type TNotificationItem = { export const NotificationItem: FC = observer((props) => { const { workspaceSlug, notificationId } = props; // hooks - const { currentSelectedNotification, setCurrentSelectedNotification } = useWorkspaceNotifications(); + const { currentSelectedNotificationId, setCurrentSelectedNotificationId } = useWorkspaceNotifications(); const { asJson: notification, markNotificationAsRead } = useNotification(notificationId); const { getIsIssuePeeked, setPeekIssue } = useIssueDetail(); // states @@ -38,22 +37,8 @@ export const NotificationItem: FC = observer((props) => { const notificationTriggeredBy = notification.triggered_by_details || undefined; const handleNotificationIssuePeekOverview = async () => { - if ( - workspaceSlug && - projectId && - issueId && - !getIsIssuePeeked(issueId) && - !isSnoozeStateModalOpen && - !customSnoozeModal - ) { - const currentSelectedNotificationPayload: TCurrentSelectedNotification = { - workspace_slug: workspaceSlug, - project_id: projectId, - issue_id: issueId, - notification_id: notification?.id, - is_inbox_issue: notification?.is_inbox_issue || false, - }; - setCurrentSelectedNotification(currentSelectedNotificationPayload); + if (workspaceSlug && projectId && issueId && !isSnoozeStateModalOpen && !customSnoozeModal) { + setCurrentSelectedNotificationId(notificationId); // make the notification as read if (notification.read_at === null) { @@ -65,7 +50,7 @@ export const NotificationItem: FC = observer((props) => { } if (notification?.is_inbox_issue === false) { - setPeekIssue({ workspaceSlug, projectId, issueId }); + !getIsIssuePeeked(issueId) && setPeekIssue({ workspaceSlug, projectId, issueId }); } else { } } @@ -77,9 +62,7 @@ export const NotificationItem: FC = observer((props) => {
{ // hooks const { getWorkspaceBySlug } = useWorkspace(); const { + currentSelectedNotificationId, unreadNotificationsCount, loader, notificationIdsByWorkspaceId, @@ -35,67 +36,75 @@ export const NotificationsSidebar: FC = observer(() => { const notificationIds = workspace ? notificationIdsByWorkspaceId(workspace.id) : undefined; if (!workspaceSlug || !workspace) return <>; - return ( -
-
- -
-
- {NOTIFICATION_TABS.map((tab) => ( -
currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)} - > + return ( +
+
+
+ +
+ +
+ {NOTIFICATION_TABS.map((tab) => (
currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)} > -
{tab.label}
- {getNumberCount(tab.count(unreadNotificationsCount))} +
{tab.label}
+
+ {getNumberCount(tab.count(unreadNotificationsCount))} +
+ {currentNotificationTab === tab.value && ( +
+ )}
- {currentNotificationTab === tab.value && ( -
- )} -
- ))} -
- - {/* applied filters */} -
- -
- - {/* rendering notifications */} - {loader === "init-loader" ? ( -
- + ))}
- ) : ( - <> - {notificationIds && notificationIds.length > 0 ? ( -
- -
- ) : ( -
- -
- )} - - )} + + {/* applied filters */} +
+ +
+ + {/* rendering notifications */} + {loader === "init-loader" ? ( +
+ +
+ ) : ( + <> + {notificationIds && notificationIds.length > 0 ? ( +
+ +
+ ) : ( +
+ +
+ )} + + )} +
); }); diff --git a/web/core/store/notifications/workspace-notifications.store.ts b/web/core/store/notifications/workspace-notifications.store.ts index bbc7e563b4..5b990df3a0 100644 --- a/web/core/store/notifications/workspace-notifications.store.ts +++ b/web/core/store/notifications/workspace-notifications.store.ts @@ -5,9 +5,9 @@ import update from "lodash/update"; import { action, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; import { - TCurrentSelectedNotification, TNotification, TNotificationFilter, + TNotificationLite, TNotificationPaginatedInfo, TNotificationPaginatedInfoQueryParams, TUnreadNotificationsCount, @@ -36,19 +36,20 @@ export interface IWorkspaceNotificationStore { unreadNotificationsCount: TUnreadNotificationsCount; notifications: Record; // notification_id -> notification currentNotificationTab: TNotificationTab; - currentSelectedNotification: TCurrentSelectedNotification; + currentSelectedNotificationId: string | undefined; paginationInfo: Omit | undefined; filters: TNotificationFilter; // computed // computed functions notificationIdsByWorkspaceId: (workspaceId: string) => string[] | undefined; + notificationLiteByNotificationId: (notificationId: string | undefined) => TNotificationLite; // helper actions mutateNotifications: (notifications: TNotification[]) => void; updateFilters: (key: T, value: TNotificationFilter[T]) => void; updateBulkFilters: (filters: Partial) => void; // actions setCurrentNotificationTab: (tab: TNotificationTab) => void; - setCurrentSelectedNotification: (notification: TCurrentSelectedNotification) => void; + setCurrentSelectedNotificationId: (notificationId: string | undefined) => void; setUnreadNotificationsCount: (type: "increment" | "decrement") => void; getUnreadNotificationsCount: (workspaceSlug: string) => Promise; getNotifications: ( @@ -70,13 +71,7 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { }; notifications: Record = {}; currentNotificationTab: TNotificationTab = ENotificationTab.ALL; - currentSelectedNotification: TCurrentSelectedNotification = { - workspace_slug: undefined, - project_id: undefined, - notification_id: undefined, - issue_id: undefined, - is_inbox_issue: false, - }; + currentSelectedNotificationId: string | undefined = undefined; paginationInfo: Omit | undefined = undefined; filters: TNotificationFilter = { type: { @@ -96,13 +91,13 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { unreadNotificationsCount: observable, notifications: observable, currentNotificationTab: observable.ref, - currentSelectedNotification: observable, + currentSelectedNotificationId: observable, paginationInfo: observable, filters: observable, // computed // helper actions setCurrentNotificationTab: action, - setCurrentSelectedNotification: action, + setCurrentSelectedNotificationId: action, setUnreadNotificationsCount: action, mutateNotifications: action, updateFilters: action, @@ -154,6 +149,24 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { return workspaceNotificationIds; }); + /** + * @description get notification lite by notification id + * @param { string } notificationId + */ + notificationLiteByNotificationId = computedFn((notificationId: string | undefined) => { + if (!notificationId) return {} as TNotificationLite; + const { workspaceSlug } = this.store.router; + const notification = this.notifications[notificationId]; + if (!notification || !workspaceSlug) return {} as TNotificationLite; + return { + workspace_slug: workspaceSlug, + project_id: notification.project, + notification_id: notification.id, + issue_id: notification.data?.issue?.id, + is_inbox_issue: notification.is_inbox_issue || false, + }; + }); + // helper functions /** * @description generate notification query params @@ -255,11 +268,11 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { /** * @description set current selected notification - * @param { TCurrentSelectedNotification } notification + * @param { string | undefined } notificationId * @returns { void } */ - setCurrentSelectedNotification = (notification: TCurrentSelectedNotification): void => { - set(this, "currentSelectedNotification", notification); + setCurrentSelectedNotificationId = (notificationId: string | undefined): void => { + set(this, "currentSelectedNotificationId", notificationId); }; /**