sendou.ink/app/components/layout/NotificationPopover.tsx
hfcRed 0e88532f3a
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
Close mobile panel on notification click
2026-03-20 04:59:36 +01:00

111 lines
2.8 KiB
TypeScript

import { Bell, ChevronRight, RefreshCcw } from "lucide-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link, useMatches, useRevalidator } from "react-router";
import {
NotificationItem,
NotificationItemDivider,
NotificationsList,
} from "~/features/notifications/components/NotificationList";
import { NOTIFICATIONS } from "~/features/notifications/notifications-contants";
import type { RootLoaderData } from "~/root";
import { NOTIFICATIONS_URL } from "~/utils/urls";
import { useMarkNotificationsAsSeen } from "../../features/notifications/notifications-hooks";
import { SendouButton } from "../elements/Button";
import styles from "./NotificationPopover.module.css";
export type LoaderNotification = NonNullable<
RootLoaderData["notifications"]
>[number];
export function useNotifications() {
const [root] = useMatches();
const notifications = (root.data as RootLoaderData | undefined)
?.notifications;
const unseenIds = React.useMemo(
() =>
notifications
?.filter((notification) => !notification.seen)
.map((notification) => notification.id) ?? [],
[notifications],
);
return { notifications, unseenIds };
}
export function NotificationContent({
notifications,
unseenIds,
onClose,
}: {
notifications: LoaderNotification[];
unseenIds: number[];
onClose?: () => void;
}) {
const { t } = useTranslation(["common"]);
const { revalidate, state } = useRevalidator();
useMarkNotificationsAsSeen(unseenIds);
return (
<>
<div className={styles.topContainer}>
<h2 className={styles.header}>
<Bell /> {t("common:notifications.title")}
</h2>
<SendouButton
icon={<RefreshCcw />}
shape="circle"
variant="minimal"
onPress={revalidate}
isDisabled={state !== "idle"}
/>
</div>
<hr className={styles.divider} />
{notifications.length === 0 ? (
<div className={styles.noNotifications}>
{t("common:notifications.empty")}
</div>
) : (
<NotificationsList>
{notifications.map((notification, i) => (
<React.Fragment key={notification.id}>
<NotificationItem
key={notification.id}
notification={notification}
onClose={onClose}
/>
{i !== notifications.length - 1 && <NotificationItemDivider />}
</React.Fragment>
))}
</NotificationsList>
)}
{notifications.length === NOTIFICATIONS.PEEK_COUNT ? (
<NotificationsFooter onClose={onClose} />
) : null}
</>
);
}
function NotificationsFooter({ onClose }: { onClose?: () => void }) {
const { t } = useTranslation(["common"]);
return (
<div>
<hr className={styles.divider} />
<Link
to={NOTIFICATIONS_URL}
className={styles.viewAllLink}
data-testid="notifications-see-all-button"
onClick={onClose}
>
{t("common:actions.viewAll")}
<ChevronRight size={14} />
</Link>
</div>
);
}