mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
111 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|