[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
This commit is contained in:
guru_sainath
2024-07-08 18:52:30 +05:30
committed by GitHub
parent a623456e63
commit 7767be2e21
10 changed files with 131 additions and 99 deletions

View File

@@ -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;

View File

@@ -6,9 +6,7 @@ import { NotificationsSidebar } from "@/components/workspace-notifications";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
return (
<div className="relative w-full h-full overflow-hidden flex items-center">
<div className="relative w-full lg:w-2/6 border-0 lg:border-r border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all overflow-hidden">
<NotificationsSidebar />
</div>
<NotificationsSidebar />
<div className="w-full h-full overflow-hidden overflow-y-auto">{children}</div>
</div>
);

View File

@@ -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)}
/>
)}
</>
) : (
<IssuePeekOverview embedIssue />
<IssuePeekOverview
embedIssue
embedRemoveCurrentNotification={() => setCurrentSelectedNotificationId(undefined)}
/>
)}
</div>
</>

View File

@@ -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<TInboxIssueActionsHeader> = observer((props) => {
@@ -56,6 +58,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
isMobileSidebar,
setIsMobileSidebar,
isNotificationEmbed = false,
embedRemoveCurrentNotification,
} = props;
// states
const [isSnoozeDateModalOpen, setIsSnoozeDateModalOpen] = useState(false);
@@ -240,6 +243,11 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
<div className="hidden relative lg:flex h-full w-full items-center justify-between gap-2 px-4">
<div className="flex items-center gap-4">
{isNotificationEmbed && (
<button onClick={embedRemoveCurrentNotification}>
<MoveRight className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
</button>
)}
{issue?.project_id && issue.sequence_id && (
<h3 className="text-base font-medium text-custom-text-300 flex-shrink-0">
{getProjectById(issue.project_id)?.identifier}-{issue.sequence_id}

View File

@@ -16,6 +16,7 @@ type TInboxContentRoot = {
isMobileSidebar: boolean;
setIsMobileSidebar: (value: boolean) => void;
isNotificationEmbed?: boolean;
embedRemoveCurrentNotification?: () => void;
};
export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
@@ -26,6 +27,7 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
isMobileSidebar,
setIsMobileSidebar,
isNotificationEmbed = false,
embedRemoveCurrentNotification,
} = props;
/// router
const router = useAppRouter();
@@ -78,6 +80,7 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
inboxIssue={inboxIssue}
isSubmitting={isSubmitting}
isNotificationEmbed={isNotificationEmbed || false}
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
/>
</div>
<div className="h-full w-full space-y-5 divide-y-2 divide-custom-border-200 overflow-y-auto px-6 py-5 vertical-scrollbar scrollbar-md">

View File

@@ -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<IIssuePeekOverview> = 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<IIssuePeekOverview> = observer((props) => {
is_archived={is_archived}
disabled={!isEditable}
embedIssue={embedIssue}
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
issueOperations={issueOperations}
/>
);

View File

@@ -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<IIssueView> = observer((props) => {
is_archived,
disabled = false,
embedIssue = false,
embedRemoveCurrentNotification,
issueOperations,
} = props;
// states
@@ -64,13 +66,16 @@ export const IssueView: FC<IIssueView> = 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<IIssueView> = observer((props) => {
}
};
useKeypress("Escape", handleKeyDown);
useKeypress("Escape", () => !embedIssue && handleKeyDown());
const handleRestore = async () => {
if (!issueOperations.restore) return;

View File

@@ -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<TNotificationItem> = 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<TNotificationItem> = 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<TNotificationItem> = 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<TNotificationItem> = observer((props) => {
<div
className={cn(
"relative p-3 py-4 flex items-center gap-2 border-b border-custom-border-200 cursor-pointer transition-all group",
currentSelectedNotification && currentSelectedNotification?.notification_id === notification?.id
? "bg-custom-background-80/30"
: "",
currentSelectedNotificationId === notification?.id ? "bg-custom-background-80/30" : "",
notification.read_at === null ? "bg-custom-primary-100/5" : ""
)}
onClick={handleNotificationIssuePeekOverview}

View File

@@ -24,6 +24,7 @@ export const NotificationsSidebar: FC = observer(() => {
// 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 (
<div className="relative w-full h-full overflow-hidden flex flex-col">
<div className="border-b border-custom-border-200">
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
</div>
<div className="flex-shrink-0 w-full h-[46px] border-b border-custom-border-200 px-5 relative flex items-center gap-2">
{NOTIFICATION_TABS.map((tab) => (
<div
key={tab.value}
className="h-full px-3 relative flex flex-col cursor-pointer"
onClick={() => currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)}
>
return (
<div
className={cn(
"relative border-0 md:border-r border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all overflow-hidden",
currentSelectedNotificationId ? "w-0 md:w-2/6" : "w-full md:w-2/6"
)}
>
<div className="relative w-full h-full overflow-hidden flex flex-col">
<div className="border-b border-custom-border-200">
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
</div>
<div className="flex-shrink-0 w-full h-[46px] border-b border-custom-border-200 px-5 relative flex items-center gap-2">
{NOTIFICATION_TABS.map((tab) => (
<div
className={cn(
`relative h-full flex justify-center items-center gap-1 text-sm transition-all`,
currentNotificationTab === tab.value
? "text-custom-primary-100"
: "text-custom-text-100 hover:text-custom-text-200"
)}
key={tab.value}
className="h-full px-3 relative flex flex-col cursor-pointer"
onClick={() => currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)}
>
<div className="font-medium">{tab.label}</div>
<div
className={cn(
`rounded-full text-xs px-1.5 py-0.5`,
currentNotificationTab === tab.value ? `bg-custom-primary-100/20` : `bg-custom-background-80/50`
`relative h-full flex justify-center items-center gap-1 text-sm transition-all`,
currentNotificationTab === tab.value
? "text-custom-primary-100"
: "text-custom-text-100 hover:text-custom-text-200"
)}
>
{getNumberCount(tab.count(unreadNotificationsCount))}
<div className="font-medium">{tab.label}</div>
<div
className={cn(
`rounded-full text-xs px-1.5 py-0.5`,
currentNotificationTab === tab.value ? `bg-custom-primary-100/20` : `bg-custom-background-80/50`
)}
>
{getNumberCount(tab.count(unreadNotificationsCount))}
</div>
</div>
{currentNotificationTab === tab.value && (
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
)}
</div>
{currentNotificationTab === tab.value && (
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
)}
</div>
))}
</div>
{/* applied filters */}
<div className="flex-shrink-0">
<AppliedFilters workspaceSlug={workspaceSlug.toString()} />
</div>
{/* rendering notifications */}
{loader === "init-loader" ? (
<div className="relative w-full h-full overflow-hidden">
<NotificationsLoader />
))}
</div>
) : (
<>
{notificationIds && notificationIds.length > 0 ? (
<div className="relative w-full h-full overflow-hidden overflow-y-auto">
<NotificationCardListRoot workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
</div>
) : (
<div className="relative w-full h-full flex justify-center items-center">
<NotificationEmptyState />
</div>
)}
</>
)}
{/* applied filters */}
<div className="flex-shrink-0">
<AppliedFilters workspaceSlug={workspaceSlug.toString()} />
</div>
{/* rendering notifications */}
{loader === "init-loader" ? (
<div className="relative w-full h-full overflow-hidden">
<NotificationsLoader />
</div>
) : (
<>
{notificationIds && notificationIds.length > 0 ? (
<div className="relative w-full h-full overflow-hidden overflow-y-auto">
<NotificationCardListRoot workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
</div>
) : (
<div className="relative w-full h-full flex justify-center items-center">
<NotificationEmptyState />
</div>
)}
</>
)}
</div>
</div>
);
});

View File

@@ -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<string, INotification>; // notification_id -> notification
currentNotificationTab: TNotificationTab;
currentSelectedNotification: TCurrentSelectedNotification;
currentSelectedNotificationId: string | undefined;
paginationInfo: Omit<TNotificationPaginatedInfo, "results"> | undefined;
filters: TNotificationFilter;
// computed
// computed functions
notificationIdsByWorkspaceId: (workspaceId: string) => string[] | undefined;
notificationLiteByNotificationId: (notificationId: string | undefined) => TNotificationLite;
// helper actions
mutateNotifications: (notifications: TNotification[]) => void;
updateFilters: <T extends keyof TNotificationFilter>(key: T, value: TNotificationFilter[T]) => void;
updateBulkFilters: (filters: Partial<TNotificationFilter>) => void;
// actions
setCurrentNotificationTab: (tab: TNotificationTab) => void;
setCurrentSelectedNotification: (notification: TCurrentSelectedNotification) => void;
setCurrentSelectedNotificationId: (notificationId: string | undefined) => void;
setUnreadNotificationsCount: (type: "increment" | "decrement") => void;
getUnreadNotificationsCount: (workspaceSlug: string) => Promise<TUnreadNotificationsCount | undefined>;
getNotifications: (
@@ -70,13 +71,7 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore {
};
notifications: Record<string, INotification> = {};
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<TNotificationPaginatedInfo, "results"> | 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);
};
/**