feat: group notifications

This commit is contained in:
Philipinho
2026-03-31 15:38:06 +01:00
parent bd42dec6be
commit 1912ef5b1c
16 changed files with 101 additions and 22 deletions
@@ -690,6 +690,8 @@
"Get notified when your comment is resolved.": "Get notified when your comment is resolved.",
"You are now watching this page": "You are now watching this page",
"You are no longer watching this page": "You are no longer watching this page",
"Direct": "Direct",
"Updates": "Updates",
"Today": "Today",
"Yesterday": "Yesterday",
"This week": "This week",
@@ -294,6 +294,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
w={popupWidth}
scrollbars={"y"}
scrollbarSize={6}
overscrollBehavior={"contain"}
styles={{ content: { minWidth: 0 } }}
>
{renderItems?.map((item, index) => {
@@ -87,7 +87,13 @@ const CommandList = ({
return flatItems.length > 0 ? (
<Paper id="slash-command" shadow="md" p="xs" withBorder>
<ScrollArea viewportRef={viewportRef} h={350} w={270} scrollbarSize={8}>
<ScrollArea
viewportRef={viewportRef}
h={350}
w={270}
scrollbarSize={8}
overscrollBehavior="contain"
>
{Object.entries(items).map(([category, categoryItems]) => (
<div key={category}>
<Text c="dimmed" mb={4} fw={500} tt="capitalize">
@@ -103,10 +109,7 @@ const CommandList = ({
})}
>
<Group>
<ActionIcon
variant="default"
component="div"
>
<ActionIcon variant="default" component="div">
<item.icon size={18} />
</ActionIcon>
@@ -49,7 +49,7 @@ const renderItems = () => {
getReferenceClientRect = props.clientRect;
popup = document.createElement("div");
popup.style.zIndex = "9999";
popup.style.zIndex = "199";
popup.style.position = "absolute";
popup.style.top = "0";
popup.style.left = "0";
@@ -77,6 +77,7 @@ export function NotificationItem({
};
const handleMarkRead = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
markReadIfNeeded();
};
@@ -3,17 +3,23 @@ import { IconBellOff } from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { useEffect, useRef } from "react";
import { NotificationItem } from "./notification-item";
import { INotification, NotificationFilter } from "../types/notification.types";
import {
INotification,
NotificationFilter,
NotificationTab,
} from "../types/notification.types";
import { groupNotificationsByTime } from "../notification.utils";
import { useNotificationsQuery } from "../queries/notification-query";
import classes from "../notification.module.css";
type NotificationListProps = {
tab: NotificationTab;
filter: NotificationFilter;
onNavigate: () => void;
};
export function NotificationList({
tab,
filter,
onNavigate,
}: NotificationListProps) {
@@ -24,7 +30,7 @@ export function NotificationList({
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useNotificationsQuery();
} = useNotificationsQuery(tab as string);
const sentinelRef = useRef<HTMLDivElement>(null);
@@ -6,6 +6,7 @@ import {
Menu,
Popover,
ScrollArea,
Tabs,
Text,
Tooltip,
} from "@mantine/core";
@@ -18,15 +19,20 @@ import {
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { NotificationList } from "./notification-list";
import { NotificationFilter } from "../types/notification.types";
import {
NotificationFilter,
NotificationTab,
} from "../types/notification.types";
import {
useMarkAllReadMutation,
useUnreadCountQuery,
} from "../queries/notification-query";
import classes from "../notification.module.css";
export function NotificationPopover() {
const { t } = useTranslation();
const [opened, setOpened] = useState(false);
const [tab, setTab] = useState<NotificationTab>("direct");
const [filter, setFilter] = useState<NotificationFilter>("all");
const { data: unreadData } = useUnreadCountQuery();
@@ -125,13 +131,27 @@ export function NotificationPopover() {
</Group>
</Group>
<Tabs
value={tab}
onChange={(value) => setTab(value as NotificationTab)}
variant="default"
color="dark"
>
<Tabs.List px="md">
<Tabs.Tab value="direct">{t("Direct")}</Tabs.Tab>
<Tabs.Tab value="updates">{t("Updates")}</Tabs.Tab>
</Tabs.List>
</Tabs>
<ScrollArea.Autosize
mah={500}
type="auto"
offsetScrollbars
scrollbarSize={6}
style={{ overscrollBehavior: "contain" }}
>
<NotificationList
tab={tab}
filter={filter}
onNavigate={() => setOpened(false)}
/>
@@ -13,3 +13,4 @@
.divider {
border-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
}
@@ -15,10 +15,10 @@ import {
export const NOTIFICATION_KEY = ["notifications"];
export const UNREAD_COUNT_KEY = ["notifications", "unread-count"];
export function useNotificationsQuery() {
export function useNotificationsQuery(type?: string) {
return useInfiniteQuery({
queryKey: NOTIFICATION_KEY,
queryFn: ({ pageParam }) => getNotifications({ cursor: pageParam }),
queryKey: [...NOTIFICATION_KEY, type],
queryFn: ({ pageParam }) => getNotifications({ cursor: pageParam, type }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) =>
lastPage.meta.hasNextPage ? lastPage.meta.nextCursor : undefined,
@@ -5,6 +5,7 @@ import { IPagination } from "@/lib/types";
export async function getNotifications(params: {
limit?: number;
cursor?: string;
type?: string;
}): Promise<IPagination<INotification>> {
const req = await api.post<IPagination<INotification>>(
"/notifications",
@@ -39,3 +39,5 @@ export type INotification = {
};
export type NotificationFilter = "all" | "unread";
export type NotificationTab = "direct" | "updates" | "all";