import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import type { TFunction } from "i18next"; import { getNotifications, markNotificationsRead } from "../../api/client"; import type { NotificationItem, AnnotationItem } from "../../types"; import { Heart, MessageCircle, Bell, PenTool, Bookmark, UserPlus, AtSign, ExternalLink, } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; import { clsx } from "clsx"; import { Avatar, EmptyState, Skeleton } from "../../components/ui"; const notificationsCache = { data: null as NotificationItem[] | null, timestamp: 0, }; function getContentType( uri: string, ): "annotation" | "highlight" | "bookmark" | "reply" | "unknown" { if (uri.includes("/at.margin.annotation/")) return "annotation"; if (uri.includes("/at.margin.highlight/")) return "highlight"; if (uri.includes("/at.margin.bookmark/")) return "bookmark"; if (uri.includes("/at.margin.reply/")) return "reply"; return "unknown"; } function getNotificationVerb( notifType: string, contentType: string, t: TFunction, subject?: AnnotationItem, ): string { switch (notifType) { case "like": switch (contentType) { case "annotation": return t("notifications.likedAnnotation"); case "highlight": return t("notifications.likedHighlight"); case "bookmark": return t("notifications.likedBookmark"); case "reply": return t("notifications.likedReply"); default: return t("notifications.likedPost"); } case "reply": { const parentUri = subject?.inReplyTo; const parentIsReply = parentUri ? getContentType(parentUri) === "reply" : false; return parentIsReply ? t("notifications.repliedToReply") : t("notifications.repliedToAnnotation"); } case "mention": return t("notifications.mentionedInAnnotation"); case "follow": return t("notifications.followedYou"); case "highlight": return t("notifications.highlightedPage"); default: return notifType; } } const NotificationIcon = ({ type }: { type: string }) => { const base = "p-2 rounded-full"; switch (type) { case "like": return (
“{quote}”
)} {body && ({body}
)} > ); } else if (contentType === "highlight") { const quote = item?.target?.selector?.exact; preview = quote ? (“{quote}”
) : null; } else if (contentType === "bookmark") { const title = item?.title || item?.target?.title; const source = item?.source || item?.target?.source; preview = ( <> {title && ({title}
)} {source && (
{text}
)} {parentUri && ({t("notifications.inReplyTo")}{" "} e.stopPropagation()} > {parentIsReply ? t("notifications.aReply") : t("notifications.anAnnotation")}
)} > ); } if (!preview) return null; return ( {preview} ); } interface NotificationsProps { initialNotifications?: NotificationItem[]; } export default function Notifications({ initialNotifications, }: NotificationsProps) { const { t } = useTranslation(); const [notifications, setNotifications] = useState