From f617937542792dce1299606ec9676603d8a3422a Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 9 Jul 2024 13:41:34 +0530 Subject: [PATCH] [WEB-1900] chore: mentions mutation, ui fix on app sidebar notification badge, and back button inbox issue notification embed (#5083) * chore: mention notification boolean field * chore: handled mentions and all notification mutation and UI fix on the app sidebar notification badge and Back redirection button on inbox issue resposiveness * chore: Moved everthing to chip * chore: cleaning up the selection when we unmount the page * chore: resolved build error --------- Co-authored-by: NarayanBavisetti --- .../plane/app/serializers/notification.py | 1 + .../plane/app/views/notification/base.py | 9 ++++++- .../types/src/workspace-notifications.d.ts | 1 + .../(projects)/notifications/page.tsx | 13 +++++++++- .../[projectId]/issues/(list)/header.tsx | 6 ++--- web/core/components/common/count-chip.tsx | 25 +++++++++++++++++++ web/core/components/common/index.ts | 1 + .../inbox/content/inbox-issue-header.tsx | 2 ++ .../content/inbox-issue-mobile-header.tsx | 10 ++++++++ .../notification-app-sidebar-option.tsx | 6 +++-- .../sidebar/notification-card/item.tsx | 2 +- .../workspace-notifications/sidebar/root.tsx | 13 +++++----- web/core/store/notifications/notification.ts | 9 +++++-- .../workspace-notifications.store.ts | 5 ++++ 14 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 web/core/components/common/count-chip.tsx diff --git a/apiserver/plane/app/serializers/notification.py b/apiserver/plane/app/serializers/notification.py index 248441bc8..a99b63e0d 100644 --- a/apiserver/plane/app/serializers/notification.py +++ b/apiserver/plane/app/serializers/notification.py @@ -12,6 +12,7 @@ class NotificationSerializer(BaseSerializer): read_only=True, source="triggered_by" ) is_inbox_issue = serializers.BooleanField(read_only=True) + is_mentioned_notification = serializers.BooleanField(read_only=True) class Meta: model = Notification diff --git a/apiserver/plane/app/views/notification/base.py b/apiserver/plane/app/views/notification/base.py index c67dec557..6cae9d02a 100644 --- a/apiserver/plane/app/views/notification/base.py +++ b/apiserver/plane/app/views/notification/base.py @@ -1,5 +1,5 @@ # Django imports -from django.db.models import Exists, OuterRef, Q +from django.db.models import Exists, OuterRef, Q, Case, When, BooleanField from django.utils import timezone # Third party imports @@ -60,6 +60,13 @@ class NotificationViewSet(BaseViewSet, BasePaginator): ) .filter(entity_name="issue") .annotate(is_inbox_issue=Exists(inbox_issue)) + .annotate( + is_mentioned_notification=Case( + When(sender__icontains="mentioned", then=True), + default=False, + output_field=BooleanField(), + ) + ) .select_related("workspace", "project", "triggered_by", "receiver") .order_by("snoozed_till", "-created_at") ) diff --git a/packages/types/src/workspace-notifications.d.ts b/packages/types/src/workspace-notifications.d.ts index 718387f32..2b32a45cb 100644 --- a/packages/types/src/workspace-notifications.d.ts +++ b/packages/types/src/workspace-notifications.d.ts @@ -51,6 +51,7 @@ export type TNotification = { archived_at: string | undefined; snoozed_till: string | undefined; is_inbox_issue: boolean | undefined; + is_mentioned_notification: boolean | undefined; workspace: string | undefined; project: string | undefined; created_at: string | undefined; diff --git a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx index 8361b15f0..5fa81a8e1 100644 --- a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { useEffect } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; // components @@ -10,7 +11,7 @@ import { IssuePeekOverview } from "@/components/issues"; // constants import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification"; // hooks -import { useUser, useWorkspace, useWorkspaceNotifications } from "@/hooks/store"; +import { useIssueDetail, useUser, useWorkspace, useWorkspaceNotifications } from "@/hooks/store"; const WorkspaceDashboardPage = observer(() => { // hooks @@ -25,6 +26,7 @@ const WorkspaceDashboardPage = observer(() => { const { membership: { fetchUserProjectInfo }, } = useUser(); + const { setPeekIssue } = useIssueDetail(); // derived values const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Notifications` : undefined; const { workspace_slug, project_id, issue_id, is_inbox_issue } = @@ -54,6 +56,15 @@ const WorkspaceDashboardPage = observer(() => { workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null ); + // clearing up the selected notifications when unmounting the page + useEffect( + () => () => { + setCurrentSelectedNotificationId(undefined); + setPeekIssue(undefined); + }, + [] + ); + return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx index 9fccc4488..3ff159244 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx @@ -11,7 +11,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; -import { BreadcrumbLink, Logo } from "@/components/common"; +import { BreadcrumbLink, CountChip, Logo } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // constants import { @@ -161,9 +161,7 @@ export const ProjectIssuesHeader = observer(() => { tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "issues" : "issue"} in this project`} position="bottom" > - - {issuesCount} - + ) : null} diff --git a/web/core/components/common/count-chip.tsx b/web/core/components/common/count-chip.tsx new file mode 100644 index 000000000..0b5820d75 --- /dev/null +++ b/web/core/components/common/count-chip.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { FC } from "react"; +// +import { cn } from "@/helpers/common.helper"; + +type TCountChip = { + count: string | number; + className?: string; +}; + +export const CountChip: FC = (props) => { + const { count, className = "" } = props; + + return ( +
+ {count} +
+ ); +}; diff --git a/web/core/components/common/index.ts b/web/core/components/common/index.ts index 1ca40f810..12f4dd648 100644 --- a/web/core/components/common/index.ts +++ b/web/core/components/common/index.ts @@ -4,3 +4,4 @@ export * from "./latest-feature-block"; export * from "./breadcrumb-link"; export * from "./logo-spinner"; export * from "./logo"; +export * from "./count-chip"; diff --git a/web/core/components/inbox/content/inbox-issue-header.tsx b/web/core/components/inbox/content/inbox-issue-header.tsx index a7aa56920..1b18f063a 100644 --- a/web/core/components/inbox/content/inbox-issue-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-header.tsx @@ -387,6 +387,8 @@ export const InboxIssueActionsHeader: FC = observer((p workspaceSlug={workspaceSlug} isMobileSidebar={isMobileSidebar} setIsMobileSidebar={setIsMobileSidebar} + isNotificationEmbed={isNotificationEmbed} + embedRemoveCurrentNotification={embedRemoveCurrentNotification} /> diff --git a/web/core/components/inbox/content/inbox-issue-mobile-header.tsx b/web/core/components/inbox/content/inbox-issue-mobile-header.tsx index f44d21d62..dd63c3bf9 100644 --- a/web/core/components/inbox/content/inbox-issue-mobile-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-mobile-header.tsx @@ -13,6 +13,7 @@ import { Link, Trash2, PanelLeft, + MoveRight, } from "lucide-react"; import { CustomMenu } from "@plane/ui"; // components @@ -44,6 +45,8 @@ type Props = { handleCopyIssueLink: () => void; isMobileSidebar: boolean; setIsMobileSidebar: (value: boolean) => void; + isNotificationEmbed: boolean; + embedRemoveCurrentNotification?: () => void; }; export const InboxIssueActionsMobileHeader: React.FC = observer((props) => { @@ -65,6 +68,8 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = handleCopyIssueLink, isMobileSidebar, setIsMobileSidebar, + isNotificationEmbed, + embedRemoveCurrentNotification, } = props; const router = useAppRouter(); const issue = inboxIssue?.issue; @@ -76,6 +81,11 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = return (
+ {isNotificationEmbed && ( + + )} setIsMobileSidebar(!isMobileSidebar)} className={cn( diff --git a/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx b/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx index 109cfcde5..3d33bdd72 100644 --- a/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx +++ b/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx @@ -3,6 +3,8 @@ import { FC } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; +// components +import { CountChip } from "@/components/common"; // helpers import { getNumberCount } from "@/helpers/string.helper"; // hooks @@ -35,8 +37,8 @@ export const NotificationAppSidebarOption: FC = o return
; return ( -
- {`${isMentionsEnabled ? `@` : ``}${getNumberCount(totalNotifications)}`} +
+
); }); 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 25fc4bfef..cd0a0d888 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx @@ -38,6 +38,7 @@ export const NotificationItem: FC = observer((props) => { const handleNotificationIssuePeekOverview = async () => { if (workspaceSlug && projectId && issueId && !isSnoozeStateModalOpen && !customSnoozeModal) { + setPeekIssue(undefined); setCurrentSelectedNotificationId(notificationId); // make the notification as read @@ -51,7 +52,6 @@ export const NotificationItem: FC = observer((props) => { if (notification?.is_inbox_issue === false) { !getIsIssuePeeked(issueId) && setPeekIssue({ workspaceSlug, projectId, issueId }); - } else { } } }; diff --git a/web/core/components/workspace-notifications/sidebar/root.tsx b/web/core/components/workspace-notifications/sidebar/root.tsx index 0cdaede73..679df0b31 100644 --- a/web/core/components/workspace-notifications/sidebar/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/root.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { CountChip } from "@/components/common"; import { NotificationsLoader, NotificationEmptyState, @@ -65,14 +66,12 @@ export const NotificationsSidebar: FC = observer(() => { )} >
{tab.label}
-
- {getNumberCount(tab.count(unreadNotificationsCount))} -
+ } + />
{currentNotificationTab === tab.value && (
diff --git a/web/core/store/notifications/notification.ts b/web/core/store/notifications/notification.ts index ae31c985e..dff2755fd 100644 --- a/web/core/store/notifications/notification.ts +++ b/web/core/store/notifications/notification.ts @@ -42,6 +42,7 @@ export class Notification implements INotification { archived_at: string | undefined = undefined; snoozed_till: string | undefined = undefined; is_inbox_issue: boolean | undefined = undefined; + is_mentioned_notification: boolean | undefined = undefined; workspace: string | undefined = undefined; project: string | undefined = undefined; created_at: string | undefined = undefined; @@ -70,6 +71,8 @@ export class Notification implements INotification { read_at: observable.ref, archived_at: observable.ref, snoozed_till: observable.ref, + is_inbox_issue: observable.ref, + is_mentioned_notification: observable.ref, workspace: observable.ref, project: observable.ref, created_at: observable.ref, @@ -102,8 +105,9 @@ export class Notification implements INotification { this.read_at = this.notification.read_at; this.archived_at = this.notification.archived_at; this.snoozed_till = this.notification.snoozed_till; - this.workspace = this.notification.workspace; this.is_inbox_issue = this.notification.is_inbox_issue; + this.is_mentioned_notification = this.notification.is_mentioned_notification; + this.workspace = this.notification.workspace; this.project = this.notification.project; this.created_at = this.notification.created_at; this.updated_at = this.notification.updated_at; @@ -132,8 +136,9 @@ export class Notification implements INotification { read_at: this.read_at, archived_at: this.archived_at, snoozed_till: this.snoozed_till, - workspace: this.workspace, is_inbox_issue: this.is_inbox_issue, + is_mentioned_notification: this.is_mentioned_notification, + workspace: this.workspace, project: this.project, created_at: this.created_at, updated_at: this.updated_at, diff --git a/web/core/store/notifications/workspace-notifications.store.ts b/web/core/store/notifications/workspace-notifications.store.ts index 5b990df3a..12791a7df 100644 --- a/web/core/store/notifications/workspace-notifications.store.ts +++ b/web/core/store/notifications/workspace-notifications.store.ts @@ -125,6 +125,11 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { ); const workspaceNotificationIds = workspaceNotifications .filter((n) => n.workspace === workspaceId) + .filter((n) => + this.currentNotificationTab === ENotificationTab.MENTIONS + ? n.is_mentioned_notification + : !n.is_mentioned_notification + ) .filter((n) => { if (!this.filters.archived && !this.filters.snoozed) { if (n.archived_at) {