forked from github/plane
Merge branch 'preview' of github.com:makeplane/plane into develop
This commit is contained in:
commit
99975d0ba0
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ArchiveRestore, Clock, MessageSquare, User2 } from "lucide-react";
|
import { ArchiveRestore, Clock, MessageSquare, MoreVertical, User2 } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -14,6 +14,7 @@ import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from
|
|||||||
import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
|
import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
|
||||||
// type
|
// type
|
||||||
import type { IUserNotification } from "@plane/types";
|
import type { IUserNotification } from "@plane/types";
|
||||||
|
import { Menu } from "@headlessui/react";
|
||||||
|
|
||||||
type NotificationCardProps = {
|
type NotificationCardProps = {
|
||||||
notification: IUserNotification;
|
notification: IUserNotification;
|
||||||
@ -40,8 +41,73 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
// states
|
||||||
|
const [showSnoozeOptions, setshowSnoozeOptions] = React.useState(false);
|
||||||
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
// refs
|
||||||
|
const snoozeRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const moreOptions = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: notification.read_at ? "Mark as unread" : "Mark as read",
|
||||||
|
icon: <MessageSquare className="h-3.5 w-3.5 text-custom-text-300" />,
|
||||||
|
onClick: () => {
|
||||||
|
markNotificationReadStatusToggle(notification.id).then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
title: notification.read_at ? "Notification marked as read" : "Notification marked as unread",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: notification.archived_at ? "Unarchive" : "Archive",
|
||||||
|
icon: notification.archived_at ? (
|
||||||
|
<ArchiveRestore className="h-3.5 w-3.5 text-custom-text-300" />
|
||||||
|
) : (
|
||||||
|
<ArchiveIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
markNotificationArchivedStatus(notification.id).then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
title: notification.archived_at ? "Notification un-archived" : "Notification archived",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const snoozeOptionOnClick = (date: Date | null) => {
|
||||||
|
if (!date) {
|
||||||
|
setSelectedNotificationForSnooze(notification.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
markSnoozeNotification(notification.id, date).then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
title: `Notification snoozed till ${renderFormattedDate(date)}`,
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// close snooze options on outside click
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: any) => {
|
||||||
|
if (snoozeRef.current && !snoozeRef.current?.contains(event.target)) {
|
||||||
|
setshowSnoozeOptions(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("mousedown", handleClickOutside, true);
|
||||||
|
document.addEventListener("touchend", handleClickOutside, true);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside, true);
|
||||||
|
document.removeEventListener("touchend", handleClickOutside, true);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (isSnoozedTabOpen && new Date(notification.snoozed_till!) < new Date()) return null;
|
if (isSnoozedTabOpen && new Date(notification.snoozed_till!) < new Date()) return null;
|
||||||
|
|
||||||
@ -87,57 +153,136 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full space-y-2.5 overflow-hidden">
|
<div className="w-full space-y-2.5 overflow-hidden">
|
||||||
{!notification.message ? (
|
<div className="flex items-start">
|
||||||
<div className="w-full break-words text-sm">
|
{!notification.message ? (
|
||||||
<span className="font-semibold">
|
<div className="w-full break-words text-sm">
|
||||||
{notification.triggered_by_details.is_bot
|
<span className="font-semibold">
|
||||||
? notification.triggered_by_details.first_name
|
{notification.triggered_by_details.is_bot
|
||||||
: notification.triggered_by_details.display_name}{" "}
|
? notification.triggered_by_details.first_name
|
||||||
</span>
|
: notification.triggered_by_details.display_name}{" "}
|
||||||
{notification.data.issue_activity.field !== "comment" && notification.data.issue_activity.verb}{" "}
|
</span>
|
||||||
{notification.data.issue_activity.field === "comment"
|
{notification.data.issue_activity.field !== "comment" && notification.data.issue_activity.verb}{" "}
|
||||||
? "commented"
|
{notification.data.issue_activity.field === "comment"
|
||||||
: notification.data.issue_activity.field === "None"
|
? "commented"
|
||||||
? null
|
: notification.data.issue_activity.field === "None"
|
||||||
: replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)}{" "}
|
? null
|
||||||
{notification.data.issue_activity.field !== "comment" && notification.data.issue_activity.field !== "None"
|
: replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)}{" "}
|
||||||
? "to"
|
{notification.data.issue_activity.field !== "comment" && notification.data.issue_activity.field !== "None"
|
||||||
: ""}
|
? "to"
|
||||||
<span className="font-semibold">
|
: ""}
|
||||||
{" "}
|
<span className="font-semibold">
|
||||||
{notification.data.issue_activity.field !== "None" ? (
|
{" "}
|
||||||
notification.data.issue_activity.field !== "comment" ? (
|
{notification.data.issue_activity.field !== "None" ? (
|
||||||
notification.data.issue_activity.field === "target_date" ? (
|
notification.data.issue_activity.field !== "comment" ? (
|
||||||
renderFormattedDate(notification.data.issue_activity.new_value)
|
notification.data.issue_activity.field === "target_date" ? (
|
||||||
) : notification.data.issue_activity.field === "attachment" ? (
|
renderFormattedDate(notification.data.issue_activity.new_value)
|
||||||
"the issue"
|
) : notification.data.issue_activity.field === "attachment" ? (
|
||||||
) : notification.data.issue_activity.field === "description" ? (
|
"the issue"
|
||||||
stripAndTruncateHTML(notification.data.issue_activity.new_value, 55)
|
) : notification.data.issue_activity.field === "description" ? (
|
||||||
|
stripAndTruncateHTML(notification.data.issue_activity.new_value, 55)
|
||||||
|
) : (
|
||||||
|
notification.data.issue_activity.new_value
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
notification.data.issue_activity.new_value
|
<span>
|
||||||
|
{`"`}
|
||||||
|
{notification.data.issue_activity.new_value.length > 55
|
||||||
|
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
||||||
|
: notification.data.issue_activity.issue_comment}
|
||||||
|
{`"`}
|
||||||
|
</span>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<span>
|
"the issue and assigned it to you."
|
||||||
{`"`}
|
)}
|
||||||
{notification.data.issue_activity.new_value.length > 55
|
</span>
|
||||||
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
</div>
|
||||||
: notification.data.issue_activity.issue_comment}
|
) : (
|
||||||
{`"`}
|
<div className="w-full break-words text-sm">
|
||||||
</span>
|
<span className="semi-bold">{notification.message}</span>
|
||||||
)
|
</div>
|
||||||
) : (
|
)}
|
||||||
"the issue and assigned it to you."
|
<div className="flex md:hidden items-start">
|
||||||
|
<Menu as="div" className={" w-min text-left"}>
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Menu.Button as={React.Fragment}>
|
||||||
|
<button
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="flex w-full items-center gap-x-2 rounded p-0.5 text-sm"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-3.5 w-3.5 text-custom-text-300" />
|
||||||
|
</button>
|
||||||
|
</Menu.Button>
|
||||||
|
{open && (
|
||||||
|
<Menu.Items className={"absolute right-0 z-10"} static>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{moreOptions.map((item) => (
|
||||||
|
<Menu.Item as="div">
|
||||||
|
{({ close }) => (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
item.onClick();
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
className="flex gap-x-2 items-center p-1.5"
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
{item.name}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
<Menu.Item as="div">
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
setshowSnoozeOptions(true);
|
||||||
|
}}
|
||||||
|
className="flex gap-x-2 items-center p-1.5"
|
||||||
|
>
|
||||||
|
<Clock className="h-3.5 w-3.5 text-custom-text-300" />
|
||||||
|
Snooze
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</Menu>
|
||||||
|
{showSnoozeOptions && (
|
||||||
|
<div
|
||||||
|
ref={snoozeRef}
|
||||||
|
className="absolute right-36 z-20 my-1 top-24 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{snoozeOptions.map((item) => (
|
||||||
|
<p
|
||||||
|
className="p-1.5"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
setshowSnoozeOptions(false);
|
||||||
|
snoozeOptionOnClick(item.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<div className="w-full break-words text-sm">
|
|
||||||
<span className="semi-bold">{notification.message}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex justify-between gap-2 text-xs">
|
<div className="flex justify-between gap-2 text-xs">
|
||||||
<p className="text-custom-text-300">
|
<p className="text-custom-text-300 line-clamp-1">
|
||||||
{truncateText(
|
{truncateText(
|
||||||
`${notification.data.issue.identifier}-${notification.data.issue.sequence_id} ${notification.data.issue.name}`,
|
`${notification.data.issue.identifier}-${notification.data.issue.sequence_id} ${notification.data.issue.name}`,
|
||||||
50
|
50
|
||||||
@ -152,43 +297,12 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="flex-shrink-0 text-custom-text-300">{calculateTimeAgo(notification.created_at)}</p>
|
<p className="flex-shrink-0 text-custom-text-300 mt-auto">{calculateTimeAgo(notification.created_at)}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-3 top-3 hidden gap-x-3 py-1 group-hover:flex">
|
<div className="absolute right-3 top-3 hidden gap-x-3 py-1 md:group-hover:flex">
|
||||||
{[
|
{moreOptions.map((item) => (
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: notification.read_at ? "Mark as unread" : "Mark as read",
|
|
||||||
icon: <MessageSquare className="h-3.5 w-3.5 text-custom-text-300" />,
|
|
||||||
onClick: () => {
|
|
||||||
markNotificationReadStatusToggle(notification.id).then(() => {
|
|
||||||
setToastAlert({
|
|
||||||
title: notification.read_at ? "Notification marked as read" : "Notification marked as unread",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: notification.archived_at ? "Unarchive" : "Archive",
|
|
||||||
icon: notification.archived_at ? (
|
|
||||||
<ArchiveRestore className="h-3.5 w-3.5 text-custom-text-300" />
|
|
||||||
) : (
|
|
||||||
<ArchiveIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
|
||||||
),
|
|
||||||
onClick: () => {
|
|
||||||
markNotificationArchivedStatus(notification.id).then(() => {
|
|
||||||
setToastAlert({
|
|
||||||
title: notification.archived_at ? "Notification un-archived" : "Notification archived",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
].map((item) => (
|
|
||||||
<Tooltip tooltipContent={item.name}>
|
<Tooltip tooltipContent={item.name}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -221,18 +335,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
snoozeOptionOnClick(item.value);
|
||||||
if (!item.value) {
|
|
||||||
setSelectedNotificationForSnooze(notification.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
markSnoozeNotification(notification.id, item.value).then(() => {
|
|
||||||
setToastAlert({
|
|
||||||
title: `Notification snoozed till ${renderFormattedDate(item.value)}`,
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ArrowLeft, CheckCheck, Clock, ListFilter, MoreVertical, RefreshCw, X } from "lucide-react";
|
import { ArrowLeft, CheckCheck, Clock, ListFilter, MoreVertical, RefreshCw, X } from "lucide-react";
|
||||||
|
// components
|
||||||
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
// ui
|
// ui
|
||||||
import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -65,7 +67,11 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between px-5 pt-5">
|
<div className="flex items-center justify-between px-5 pt-5">
|
||||||
<h2 className="mb-2 text-xl font-semibold">Notifications</h2>
|
<div className="flex items-center gap-x-2 ">
|
||||||
|
<SidebarHamburgerToggle />
|
||||||
|
<h2 className="md:text-xl md:font-semibold">Notifications</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-x-4 text-custom-text-200">
|
<div className="flex items-center justify-center gap-x-4 text-custom-text-200">
|
||||||
<Tooltip tooltipContent="Refresh">
|
<Tooltip tooltipContent="Refresh">
|
||||||
<button
|
<button
|
||||||
@ -128,11 +134,13 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
<Tooltip tooltipContent="Close">
|
<div className="hidden md:block">
|
||||||
<button type="button" onClick={() => closePopover()}>
|
<Tooltip tooltipContent="Close">
|
||||||
<X className="h-3.5 w-3.5" />
|
<button type="button" onClick={() => closePopover()}>
|
||||||
</button>
|
<X className="h-3.5 w-3.5" />
|
||||||
</Tooltip>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 w-full border-b border-custom-border-300 px-5">
|
<div className="mt-5 w-full border-b border-custom-border-300 px-5">
|
||||||
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useApplication } from "hooks/store";
|
import { useApplication } from "hooks/store";
|
||||||
import useUserNotification from "hooks/use-user-notifications";
|
import useUserNotification from "hooks/use-user-notifications";
|
||||||
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// components
|
// components
|
||||||
import { EmptyState } from "components/common";
|
import { EmptyState } from "components/common";
|
||||||
import { SnoozeNotificationModal, NotificationCard, NotificationHeader } from "components/notifications";
|
import { SnoozeNotificationModal, NotificationCard, NotificationHeader } from "components/notifications";
|
||||||
@ -15,8 +16,12 @@ import emptyNotification from "public/empty-state/notification.svg";
|
|||||||
import { getNumberCount } from "helpers/string.helper";
|
import { getNumberCount } from "helpers/string.helper";
|
||||||
|
|
||||||
export const NotificationPopover = observer(() => {
|
export const NotificationPopover = observer(() => {
|
||||||
|
// states
|
||||||
|
const [isActive, setIsActive] = React.useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { theme: themeStore } = useApplication();
|
const { theme: themeStore } = useApplication();
|
||||||
|
// refs
|
||||||
|
const notificationPopoverRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
notifications,
|
notifications,
|
||||||
@ -44,8 +49,11 @@ export const NotificationPopover = observer(() => {
|
|||||||
setFetchNotifications,
|
setFetchNotifications,
|
||||||
markAllNotificationsAsRead,
|
markAllNotificationsAsRead,
|
||||||
} = useUserNotification();
|
} = useUserNotification();
|
||||||
|
|
||||||
const isSidebarCollapsed = themeStore.sidebarCollapsed;
|
const isSidebarCollapsed = themeStore.sidebarCollapsed;
|
||||||
|
useOutsideClickDetector(notificationPopoverRef, () => {
|
||||||
|
// if snooze modal is open, then don't close the popover
|
||||||
|
if (selectedNotificationForSnooze === null) setIsActive(false);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -54,141 +62,142 @@ export const NotificationPopover = observer(() => {
|
|||||||
onClose={() => setSelectedNotificationForSnooze(null)}
|
onClose={() => setSelectedNotificationForSnooze(null)}
|
||||||
onSubmit={markSnoozeNotification}
|
onSubmit={markSnoozeNotification}
|
||||||
notification={notifications?.find((notification) => notification.id === selectedNotificationForSnooze) || null}
|
notification={notifications?.find((notification) => notification.id === selectedNotificationForSnooze) || null}
|
||||||
onSuccess={() => {
|
onSuccess={() => setSelectedNotificationForSnooze(null)}
|
||||||
setSelectedNotificationForSnooze(null);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Popover className="relative w-full">
|
<Popover ref={notificationPopoverRef} className="md:relative w-full">
|
||||||
{({ open: isActive, close: closePopover }) => {
|
<>
|
||||||
if (isActive) setFetchNotifications(true);
|
<Tooltip tooltipContent="Notifications" position="right" className="ml-2" disabled={!isSidebarCollapsed}>
|
||||||
|
<button
|
||||||
|
className={`group relative flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||||
|
isActive
|
||||||
|
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||||
|
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||||
|
} ${isSidebarCollapsed ? "justify-center" : ""}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.innerWidth < 768) themeStore.toggleSidebar();
|
||||||
|
if (!isActive) setFetchNotifications(true);
|
||||||
|
setIsActive(!isActive);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bell className="h-4 w-4" />
|
||||||
|
{isSidebarCollapsed ? null : <span>Notifications</span>}
|
||||||
|
{totalNotificationCount && totalNotificationCount > 0 ? (
|
||||||
|
isSidebarCollapsed ? (
|
||||||
|
<span className="absolute right-3.5 top-2 h-2 w-2 rounded-full bg-custom-primary-300" />
|
||||||
|
) : (
|
||||||
|
<span className="ml-auto rounded-full bg-custom-primary-300 px-1.5 text-xs text-white">
|
||||||
|
{getNumberCount(totalNotificationCount)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<Transition
|
||||||
|
show={isActive}
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-200"
|
||||||
|
enterFrom="opacity-0 translate-y-1"
|
||||||
|
enterTo="opacity-100 translate-y-0"
|
||||||
|
leave="transition ease-in duration-150"
|
||||||
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
|
leaveTo="opacity-0 translate-y-1"
|
||||||
|
>
|
||||||
|
<Popover.Panel
|
||||||
|
className="absolute top-0 left-[280px] md:-top-36 md:ml-8 md:h-[50vh] z-10 flex h-full w-[100vw] flex-col rounded-xl md:border border-custom-border-300 bg-custom-background-100 shadow-lg md:left-full md:w-[36rem]"
|
||||||
|
static
|
||||||
|
>
|
||||||
|
<NotificationHeader
|
||||||
|
notificationCount={notificationCount}
|
||||||
|
notificationMutate={notificationMutate}
|
||||||
|
closePopover={() => setIsActive(false)}
|
||||||
|
isRefreshing={isRefreshing}
|
||||||
|
snoozed={snoozed}
|
||||||
|
archived={archived}
|
||||||
|
readNotification={readNotification}
|
||||||
|
selectedTab={selectedTab}
|
||||||
|
setSnoozed={setSnoozed}
|
||||||
|
setArchived={setArchived}
|
||||||
|
setReadNotification={setReadNotification}
|
||||||
|
setSelectedTab={setSelectedTab}
|
||||||
|
markAllNotificationsAsRead={markAllNotificationsAsRead}
|
||||||
|
/>
|
||||||
|
|
||||||
return (
|
{notifications ? (
|
||||||
<>
|
notifications.length > 0 ? (
|
||||||
<Tooltip tooltipContent="Notifications" position="right" className="ml-2" disabled={!isSidebarCollapsed}>
|
<div className="h-full overflow-y-auto">
|
||||||
<Popover.Button
|
<div className="divide-y divide-custom-border-100">
|
||||||
className={`group relative flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
{notifications.map((notification) => (
|
||||||
isActive
|
<NotificationCard
|
||||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
key={notification.id}
|
||||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
isSnoozedTabOpen={snoozed}
|
||||||
} ${isSidebarCollapsed ? "justify-center" : ""}`}
|
closePopover={() => setIsActive(false)}
|
||||||
>
|
notification={notification}
|
||||||
<Bell className="h-4 w-4" />
|
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
||||||
{isSidebarCollapsed ? null : <span>Notifications</span>}
|
markNotificationReadStatus={markNotificationAsRead}
|
||||||
{totalNotificationCount && totalNotificationCount > 0 ? (
|
markNotificationReadStatusToggle={markNotificationReadStatus}
|
||||||
isSidebarCollapsed ? (
|
setSelectedNotificationForSnooze={setSelectedNotificationForSnooze}
|
||||||
<span className="absolute right-3.5 top-2 h-2 w-2 rounded-full bg-custom-primary-300" />
|
markSnoozeNotification={markSnoozeNotification}
|
||||||
) : (
|
|
||||||
<span className="ml-auto rounded-full bg-custom-primary-300 px-1.5 text-xs text-white">
|
|
||||||
{getNumberCount(totalNotificationCount)}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</Popover.Button>
|
|
||||||
</Tooltip>
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute -top-36 left-0 z-10 ml-8 flex h-[50vh] w-[20rem] flex-col rounded-xl border border-custom-border-300 bg-custom-background-100 shadow-lg md:left-full md:w-[36rem]">
|
|
||||||
<NotificationHeader
|
|
||||||
notificationCount={notificationCount}
|
|
||||||
notificationMutate={notificationMutate}
|
|
||||||
closePopover={closePopover}
|
|
||||||
isRefreshing={isRefreshing}
|
|
||||||
snoozed={snoozed}
|
|
||||||
archived={archived}
|
|
||||||
readNotification={readNotification}
|
|
||||||
selectedTab={selectedTab}
|
|
||||||
setSnoozed={setSnoozed}
|
|
||||||
setArchived={setArchived}
|
|
||||||
setReadNotification={setReadNotification}
|
|
||||||
setSelectedTab={setSelectedTab}
|
|
||||||
markAllNotificationsAsRead={markAllNotificationsAsRead}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{notifications ? (
|
|
||||||
notifications.length > 0 ? (
|
|
||||||
<div className="h-full overflow-y-auto">
|
|
||||||
<div className="divide-y divide-custom-border-100">
|
|
||||||
{notifications.map((notification) => (
|
|
||||||
<NotificationCard
|
|
||||||
key={notification.id}
|
|
||||||
isSnoozedTabOpen={snoozed}
|
|
||||||
closePopover={closePopover}
|
|
||||||
notification={notification}
|
|
||||||
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
|
||||||
markNotificationReadStatus={markNotificationAsRead}
|
|
||||||
markNotificationReadStatusToggle={markNotificationReadStatus}
|
|
||||||
setSelectedNotificationForSnooze={setSelectedNotificationForSnooze}
|
|
||||||
markSnoozeNotification={markSnoozeNotification}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{isLoadingMore && (
|
|
||||||
<div className="my-6 flex items-center justify-center text-sm">
|
|
||||||
<div role="status">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="mr-2 h-6 w-6 animate-spin fill-blue-600 text-custom-text-200"
|
|
||||||
viewBox="0 0 100 101"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
||||||
fill="currentFill"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span className="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
<p>Loading notifications</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasMore && !isLoadingMore && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="my-6 flex w-full items-center justify-center text-sm font-medium text-custom-primary-100"
|
|
||||||
disabled={isLoadingMore}
|
|
||||||
onClick={() => {
|
|
||||||
setSize((prev) => prev + 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Load More
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="grid h-full w-full scale-75 place-items-center overflow-hidden">
|
|
||||||
<EmptyState
|
|
||||||
title="You're updated with all the notifications"
|
|
||||||
description="You have read all the notifications."
|
|
||||||
image={emptyNotification}
|
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{isLoadingMore && (
|
||||||
|
<div className="my-6 flex items-center justify-center text-sm">
|
||||||
|
<div role="status">
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="mr-2 h-6 w-6 animate-spin fill-blue-600 text-custom-text-200"
|
||||||
|
viewBox="0 0 100 101"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||||
|
fill="currentFill"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p>Loading notifications</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
) : (
|
{hasMore && !isLoadingMore && (
|
||||||
<Loader className="space-y-4 overflow-y-auto p-5">
|
<button
|
||||||
<Loader.Item height="50px" />
|
type="button"
|
||||||
<Loader.Item height="50px" />
|
className="my-6 flex w-full items-center justify-center text-sm font-medium text-custom-primary-100"
|
||||||
<Loader.Item height="50px" />
|
disabled={isLoadingMore}
|
||||||
<Loader.Item height="50px" />
|
onClick={() => {
|
||||||
<Loader.Item height="50px" />
|
setSize((prev) => prev + 1);
|
||||||
</Loader>
|
}}
|
||||||
)}
|
>
|
||||||
</Popover.Panel>
|
Load More
|
||||||
</Transition>
|
</button>
|
||||||
</>
|
)}
|
||||||
);
|
</div>
|
||||||
}}
|
) : (
|
||||||
|
<div className="grid h-full w-full scale-75 place-items-center overflow-hidden">
|
||||||
|
<EmptyState
|
||||||
|
title="You're updated with all the notifications"
|
||||||
|
description="You have read all the notifications."
|
||||||
|
image={emptyNotification}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Loader className="space-y-4 overflow-y-auto p-5">
|
||||||
|
<Loader.Item height="50px" />
|
||||||
|
<Loader.Item height="50px" />
|
||||||
|
<Loader.Item height="50px" />
|
||||||
|
<Loader.Item height="50px" />
|
||||||
|
<Loader.Item height="50px" />
|
||||||
|
</Loader>
|
||||||
|
)}
|
||||||
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
</Popover>
|
</Popover>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -109,7 +109,12 @@ export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
// This is a workaround to fix the issue of the Notification popover modal close on closing this modal
|
||||||
|
const closeTimeout = setTimeout(() => {
|
||||||
|
onClose();
|
||||||
|
clearTimeout(closeTimeout);
|
||||||
|
}, 50);
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
reset({ ...defaultValues });
|
reset({ ...defaultValues });
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
@ -142,7 +147,7 @@ export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
|
|||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
|
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all w-full sm:w-full sm:!max-w-2xl">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
@ -156,8 +161,8 @@ export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 flex items-center gap-3">
|
<div className="mt-5 flex flex-col md:!flex-row md:items-center gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1 pb-3 md:pb-0">
|
||||||
<h6 className="mb-2 block text-sm font-medium text-custom-text-400">Pick a date</h6>
|
<h6 className="mb-2 block text-sm font-medium text-custom-text-400">Pick a date</h6>
|
||||||
<Controller
|
<Controller
|
||||||
name="date"
|
name="date"
|
||||||
|
Loading…
Reference in New Issue
Block a user