@rpcbase/ui 0.282.0 → 0.284.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index419.js","names":["onClick"],"sources":["../src/components/Notifications/NotificationsButton.tsx"],"sourcesContent":["import { Bell } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n\nexport function NotificationsButton({\n unseenCount,\n unreadCount,\n onClick,\n className,\n}: {\n unseenCount?: number\n unreadCount?: number\n onClick?: () => void\n className?: string\n}) {\n const rawCount = Number.isFinite(unseenCount) ? unseenCount : unreadCount\n const count = Number.isFinite(rawCount) ? Math.max(0, Math.floor(rawCount ?? 0)) : 0\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent\",\n className,\n )}\n aria-label={count > 0 ? `Notifications (${count} new)` : \"Notifications\"}\n >\n <Bell className=\"h-4 w-4\" />\n {count > 0 ? (\n <span\n className=\"absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-4 text-primary-foreground\"\n aria-hidden=\"true\"\n >\n {count > 99 ? \"99+\" : String(count)}\n </span>\n ) : null}\n </button>\n )\n}\n"],"mappings":";;;;;AAKA,SAAO,EAAA,GAAA;CAAA,IAAA,IAAA,EAAA,EAAA,GAA6B,EAAA,gBAAA,gBAAA,YAAA,iBAAA,GAWlC,IAAiB,OAAM,SAAU,CAAuC,IAAvD,IAAA,GAAwD;CAAA,AAAA,EAAA,OAAA,IACW,IAAA,EAAA,MAAtE,IAAA,OAAM,SAAU,CAAqD,IAAzC,KAAI,IAAK,GAAG,KAAI,MAAO,KAAA,CAAa,CAAK,IAArE,GAAsE,EAAA,KAAA,GAAA,EAAA,KAAA;CAApF,IAAA,IAAc,GAAsE;CAAA,AAAA,EAAA,OAAA,IAS/E,IAAA,EAAA,MAHU,IAAA,EACT,4HACA,CACF,GAAC,EAAA,KAAA,GAAA,EAAA,KAAA;CACW,IAAA,IAAA,IAAQ,IAAR,kBAA8B,EAAK,SAAnC,iBAA4D;CAAA,AAAA,EAAA,OAAA,OAAA,IAAA,2BAAA,KAExE,IAAA,kBAAC,GAAD,EAAgB,WAAA,UAAS,CAAA,GAAG,EAAA,KAAA,KAAA,IAAA,EAAA;CAAA,IAAA;CAAA,AAAA,EAAA,OAAA,IAQpB,IAAA,EAAA,MAPP,IAAA,IAAQ,IACP,kBAAA,QAAA;EACY,WAAA;EACE,eAAA;EAEX,UAAA,IAAQ,KAAR,QAAqB,OAAO,CAAK;CAC7B,CAAA,IANR,MAOO,EAAA,KAAA,GAAA,EAAA,KAAA;CAAA,IAAA;CACD,OADC,EAAA,OAAA,KAAA,EAAA,OAAA,KAAA,EAAA,OAAA,KAAA,EAAA,QAAA,KAjBV,IAAA,kBAAA,UAAA;EACO,MAAA;EACI;EACE,WAAA;EAIC,cAAA;EAPd,UAAA,CASE,GACC,CAQM;KAAA,EAAA,KAAA,GAAA,EAAA,KAAA,GAAA,EAAA,KAAA,GAAA,EAAA,MAAA,GAAA,EAAA,MAAA,KAAA,IAAA,EAAA,KAlBT;AAkBS"}
1
+ {"version":3,"file":"index419.js","names":["{\n unseenCount,\n unreadCount,\n onClick,\n className,\n}: {\n unseenCount?: number\n unreadCount?: number\n onClick?: () => void\n className?: string\n}","Number.isFinite(rawCount) ? Math.max(0, Math.floor(rawCount ?? 0)) : 0","cn(\n \"relative inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent\",\n className,\n )","<Bell className=\"h-4 w-4\" />","count > 0 ? (\n <span\n className=\"absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-4 text-primary-foreground\"\n aria-hidden=\"true\"\n >\n {count > 99 ? \"99+\" : String(count)}\n </span>\n ) : null","<button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent\",\n className,\n )}\n aria-label={count > 0 ? `Notifications (${count} new)` : \"Notifications\"}\n >\n <Bell className=\"h-4 w-4\" />\n {count > 0 ? (\n <span\n className=\"absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-4 text-primary-foreground\"\n aria-hidden=\"true\"\n >\n {count > 99 ? \"99+\" : String(count)}\n </span>\n ) : null}\n </button>","count > 0 ? `Notifications (${count} new)` : \"Notifications\""],"sources":["../src/components/Notifications/NotificationsButton.tsx"],"sourcesContent":["import { Bell } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n\nexport function NotificationsButton({\n unseenCount,\n unreadCount,\n onClick,\n className,\n}: {\n unseenCount?: number\n unreadCount?: number\n onClick?: () => void\n className?: string\n}) {\n const rawCount = Number.isFinite(unseenCount) ? unseenCount : unreadCount\n const count = Number.isFinite(rawCount) ? Math.max(0, Math.floor(rawCount ?? 0)) : 0\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent\",\n className,\n )}\n aria-label={count > 0 ? `Notifications (${count} new)` : \"Notifications\"}\n >\n <Bell className=\"h-4 w-4\" />\n {count > 0 ? (\n <span\n className=\"absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-4 text-primary-foreground\"\n aria-hidden=\"true\"\n >\n {count > 99 ? \"99+\" : String(count)}\n </span>\n ) : null}\n </button>\n )\n}\n"],"mappings":";;;;;AAKA,SAAgB,EAAA,GAUb;gBAViC,EAClC,gBACA,gBACA,YACA,iBAJkCA,GAW5B,IAAW,OAAO,SAAS,CAAW,IAAI,IAAc,GAChDC;CAAgB,AAAA,EAAA,OAAA,gBAAhB,IAAA,OAAO,SAAS,CAAQ,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,KAAY,CAAC,CAAC,IAAI,GAArD,EAAA,KAAA;CAA9B,IAAM,IAAQA,GAMCC;CAET,AAAA,EAAA,OAAA,gBAFS,IAAA,EACT,4HACA,CACF,GADE,EAAA,KAAA;CAEU,IAAA,IAAA,IAAQ,IAAI,kBAAkB,EAAM,SAAS,iBAEzDC;qDAAC,IAAA,kBAAA,GAAD,EAAM,WAAU,UAAW,CAAA;CAC1BC,IAAAA;0BAAQ,IAAA,IAAA,IACP,kBAAC,QAAD;EACE,WAAU;EACV,eAAY;EAEX,UAAA,IAAQ,KAAK,QAAQ,OAAO,CAAK;CAC9B,CAAA,IACJ,MAPH,EAAA,KAAA;CAVHC,IAAAA;CADF,OAGa,EAAA,OAAA,KAAA,EAAA,OACEH,KAAAA,EAAAA,OAICI,KAAAA,EAAAA,QAGXF,KAVH,IAAA,kBAAC,UAAD;EACE,MAAK;EACI;EACT,WAAWF;EAIX,cAAYI;EAPd,UAAA,CASEH,GACCC,CAQK;KAhBG,EAAA,KAAA,GACEF,EAAAA,KAAAA,GAICI,EAAAA,KAAAA,GAGXF,EAAAA,MAAAA,2BAVHC;AAoBJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"index420.js","names":["n","unreadOnly","res","json","count","id","current","target","group","requesting","onEnable","event","actionLabel","createdAtLabel","menuButtonRef","menuOpen","menuRef"],"sources":["../src/components/Notifications/NotificationsDrawer.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type Context } from \"react\"\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\"\nimport { Loader2, MoreVertical, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport type { NotificationItem, NotificationsRealtimeState } from \"./types\"\n\n\ntype ListResponse = {\n ok?: boolean\n error?: string\n notifications?: NotificationItem[]\n unreadCount?: number\n unseenCount?: number\n}\n\ntype DayGroup = {\n key: string\n label: string\n items: NotificationItem[]\n}\n\ntype BrowserNotificationPermission = NotificationPermission | \"unsupported\"\ntype NotificationActionMode = \"navigate\" | \"merge_current_search\"\n\ntype NotificationAction = {\n link: string\n mode: NotificationActionMode\n}\n\nconst toMs = (iso: string): number => {\n const ms = Date.parse(iso)\n return Number.isFinite(ms) ? ms : 0\n}\n\nconst getDayKey = (iso: string): string => {\n const date = new Date(toMs(iso))\n if (!Number.isFinite(date.getTime())) return \"unknown\"\n\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, \"0\")\n const day = String(date.getDate()).padStart(2, \"0\")\n\n return `${year}-${month}-${day}`\n}\n\nconst formatDayLabel = (key: string): string => {\n if (key === \"unknown\") return \"Unknown date\"\n const [yearRaw, monthRaw, dayRaw] = key.split(\"-\")\n const year = Number(yearRaw)\n const month = Number(monthRaw)\n const day = Number(dayRaw)\n\n if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return \"Unknown date\"\n\n const date = new Date(year, month - 1, day)\n if (!Number.isFinite(date.getTime())) return \"Unknown date\"\n\n const now = new Date()\n const includeYear = now.getFullYear() !== year\n return date.toLocaleDateString(undefined, includeYear\n ? { weekday: \"short\", month: \"short\", day: \"numeric\", year: \"numeric\" }\n : { weekday: \"short\", month: \"short\", day: \"numeric\" })\n}\n\nconst getNotificationLink = (item: NotificationItem): string => {\n const dataLink = item.data?.link?.trim()\n if (dataLink) return dataLink\n\n const platformWebpush = item.platform?.webpush\n if (!platformWebpush || typeof platformWebpush !== \"object\" || Array.isArray(platformWebpush)) return \"\"\n const fcmOptions = (platformWebpush as { fcmOptions?: unknown }).fcmOptions\n if (!fcmOptions || typeof fcmOptions !== \"object\" || Array.isArray(fcmOptions)) return \"\"\n const link = (fcmOptions as { link?: unknown }).link\n return typeof link === \"string\" ? link.trim() : \"\"\n}\n\nconst getNotificationActionMode = (item: NotificationItem): NotificationActionMode => {\n return item.data?.linkMode === \"merge_current_search\" ? \"merge_current_search\" : \"navigate\"\n}\n\nconst getNotificationAction = (item: NotificationItem): NotificationAction | null => {\n const link = getNotificationLink(item)\n if (!link) return null\n return {\n link,\n mode: getNotificationActionMode(item),\n }\n}\n\nconst getNotificationActionLabel = (action: NotificationAction | null): string => {\n if (!action) return \"\"\n return action.mode === \"merge_current_search\" ? \"Open\" : action.link\n}\n\nconst pushUrlWithoutReload = (url: string): void => {\n window.history.pushState(window.history.state, \"\", url)\n window.dispatchEvent(new PopStateEvent(\"popstate\", { state: window.history.state }))\n}\n\nconst applyNotificationAction = (action: NotificationAction): void => {\n if (action.mode === \"merge_current_search\") {\n const currentUrl = new URL(window.location.href)\n const actionUrl = new URL(action.link, currentUrl)\n\n actionUrl.searchParams.forEach((value, key) => {\n currentUrl.searchParams.set(key, value)\n })\n if (actionUrl.hash) {\n currentUrl.hash = actionUrl.hash\n }\n\n pushUrlWithoutReload(`${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`)\n return\n }\n\n window.location.href = action.link\n}\n\nconst groupByDay = (items: NotificationItem[]): DayGroup[] => {\n const groups: DayGroup[] = []\n let currentKey: string | null = null\n\n for (const item of items) {\n const key = getDayKey(item.createdAt)\n if (key !== currentKey) {\n currentKey = key\n groups.push({ key, label: formatDayLabel(key), items: [] })\n }\n groups[groups.length - 1]!.items.push(item)\n }\n\n return groups\n}\n\nconst getBrowserNotificationPermission = (): BrowserNotificationPermission => {\n if (typeof window === \"undefined\") return \"unsupported\"\n if (!(\"Notification\" in window)) return \"unsupported\"\n return window.Notification.permission\n}\n\nconst EmptyRealtimeContext = createContext<NotificationsRealtimeState | null>(null)\n\nexport function NotificationsDrawer({\n open,\n onOpenChange,\n className,\n limit = 50,\n defaultUnreadOnly = false,\n realtimeContext,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n className?: string\n limit?: number\n defaultUnreadOnly?: boolean\n realtimeContext?: Context<NotificationsRealtimeState | null>\n}) {\n const realtime = useContext(realtimeContext ?? EmptyRealtimeContext)\n\n const [items, setItems] = useState<NotificationItem[]>([])\n const [unreadCount, setUnreadCount] = useState<number>(0)\n const [unseenCount, setUnseenCount] = useState<number>(0)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [unreadOnly, setUnreadOnly] = useState<boolean>(defaultUnreadOnly)\n const [browserNotificationPermission, setBrowserNotificationPermission] = useState<BrowserNotificationPermission>(() =>\n getBrowserNotificationPermission(),\n )\n const [browserNotificationPermissionRequesting, setBrowserNotificationPermissionRequesting] = useState(false)\n\n const mergedItems = realtime?.notifications ?? items\n const mergedUnreadCount = realtime?.unreadCount ?? unreadCount\n const mergedUnseenCount = realtime?.unseenCount ?? unseenCount\n const mergedLoading = realtime?.loading ?? loading\n const mergedError = realtime?.error\n ? (realtime.error instanceof Error ? realtime.error.message : String(realtime.error))\n : error\n\n const visibleItems = useMemo(() => mergedItems.slice(0, limit), [limit, mergedItems])\n\n const unread = useMemo(() => visibleItems.filter((n) => !n.readAt), [visibleItems])\n const read = useMemo(() => visibleItems.filter((n) => Boolean(n.readAt)), [visibleItems])\n const unreadGroups = useMemo(() => groupByDay(unread), [unread])\n const readGroups = useMemo(() => groupByDay(read), [read])\n\n const refresh = useCallback(async ({ unreadOnly, markSeen }: { unreadOnly: boolean; markSeen: boolean }) => {\n setLoading(true)\n setError(null)\n\n try {\n const res = await fetch(\"/api/rb/notifications\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify({ limit, unreadOnly, markSeen }),\n })\n\n const json = (await res.json().catch(() => null)) as ListResponse | null\n if (!res.ok || !json?.ok) {\n setError(json?.error || \"Unable to load notifications.\")\n return\n }\n\n const nextItems = Array.isArray(json.notifications) ? json.notifications : []\n setItems(nextItems)\n setUnreadCount(Number.isFinite(json.unreadCount) ? Math.max(0, Math.floor(json.unreadCount ?? 0)) : 0)\n setUnseenCount(Number.isFinite(json.unseenCount) ? Math.max(0, Math.floor(json.unseenCount ?? 0)) : 0)\n } catch {\n setError(\"Unable to load notifications.\")\n } finally {\n setLoading(false)\n }\n }, [limit])\n\n useEffect(() => {\n if (!open) return\n if (realtime) {\n void (async () => {\n try {\n await fetch(\"/api/rb/notifications\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n keepalive: true,\n body: JSON.stringify({ limit: 1, markSeen: true }),\n })\n } catch {\n return\n }\n })()\n return\n }\n void refresh({ unreadOnly, markSeen: true })\n }, [open, realtime, refresh, unreadOnly])\n\n useEffect(() => {\n if (!open) return\n if (realtime) return\n void refresh({ unreadOnly, markSeen: false })\n }, [open, realtime, refresh, unreadOnly])\n\n useEffect(() => {\n if (!open) return\n\n setBrowserNotificationPermission(getBrowserNotificationPermission())\n\n if (typeof navigator === \"undefined\" || !navigator.permissions?.query) return\n\n let permissionStatus: PermissionStatus | null = null\n let disposed = false\n const syncPermission = () => {\n setBrowserNotificationPermission(getBrowserNotificationPermission())\n }\n\n try {\n void navigator.permissions.query({ name: \"notifications\" as PermissionName }).then((status) => {\n if (disposed) return\n permissionStatus = status\n syncPermission()\n status.addEventListener(\"change\", syncPermission)\n }).catch(() => {\n // Notifications are not exposed through the Permissions API in every browser.\n })\n } catch {\n return\n }\n\n return () => {\n disposed = true\n permissionStatus?.removeEventListener(\"change\", syncPermission)\n }\n }, [open])\n\n const requestBrowserNotificationPermission = async () => {\n if (typeof window === \"undefined\" || !(\"Notification\" in window)) {\n setBrowserNotificationPermission(\"unsupported\")\n return\n }\n\n setBrowserNotificationPermissionRequesting(true)\n try {\n const nextPermission = await window.Notification.requestPermission()\n setBrowserNotificationPermission(nextPermission)\n } catch {\n setBrowserNotificationPermission(getBrowserNotificationPermission())\n } finally {\n setBrowserNotificationPermissionRequesting(false)\n }\n }\n\n const markAsRead = async (id: string) => {\n try {\n const res = await fetch(`/api/rb/notifications/${encodeURIComponent(id)}/read`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n keepalive: true,\n body: \"{}\",\n })\n\n const json = (await res.json().catch(() => null)) as { ok?: boolean } | null\n if (!res.ok || !json?.ok) return\n\n const nowIso = new Date().toISOString()\n if (realtime) return\n setItems((current) => {\n const target = current.find((n) => n.id === id)\n if (target && !target.readAt) {\n setUnreadCount((count) => Math.max(0, count - 1))\n }\n if (target && !target.seenAt) {\n setUnseenCount((count) => Math.max(0, count - 1))\n }\n return current.map((n) => (n.id === id && !n.readAt ? { ...n, readAt: nowIso, ...(n.seenAt ? {} : { seenAt: nowIso }) } : n))\n })\n } catch {\n return\n }\n }\n\n const archive = async (id: string) => {\n try {\n const res = await fetch(`/api/rb/notifications/${encodeURIComponent(id)}/archive`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: \"{}\",\n })\n\n const json = (await res.json().catch(() => null)) as { ok?: boolean } | null\n if (!res.ok || !json?.ok) return\n\n if (realtime) return\n setItems((current) => {\n const target = current.find((n) => n.id === id)\n if (target && !target.readAt) {\n setUnreadCount((count) => Math.max(0, count - 1))\n }\n if (target && !target.seenAt) {\n setUnseenCount((count) => Math.max(0, count - 1))\n }\n return current.filter((n) => n.id !== id)\n })\n } catch {\n return\n }\n }\n\n const requestClose = () => {\n onOpenChange(false)\n }\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (nextOpen) {\n onOpenChange(true)\n return\n }\n\n requestClose()\n }\n\n return (\n <DialogPrimitive.Root open={open} onOpenChange={handleOpenChange}>\n <DialogPrimitive.Portal>\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black/50\" onClick={requestClose} />\n <DialogPrimitive.Popup\n className={cn(\n \"fixed right-0 top-0 z-50 flex h-full w-full max-w-md flex-col border-l bg-background shadow-xl outline-none\",\n className,\n )}\n >\n <header className=\"flex items-center justify-between gap-2 border-b px-4 py-3\">\n <div className=\"flex min-w-0 flex-col\">\n <DialogPrimitive.Title className=\"text-sm font-semibold\">Notifications</DialogPrimitive.Title>\n <span className=\"text-xs text-muted-foreground\">\n {mergedUnreadCount > 0 ? `${mergedUnreadCount} unread` : mergedUnseenCount > 0 ? `${mergedUnseenCount} new` : \"All caught up\"}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setUnreadOnly((v) => !v)}\n className={cn(\n \"rounded-md border px-2 py-1 text-xs\",\n unreadOnly ? \"bg-accent text-foreground\" : \"bg-background text-muted-foreground hover:bg-accent\",\n )}\n >\n {unreadOnly ? \"Unread\" : \"All\"}\n </button>\n <DialogPrimitive.Close\n type=\"button\"\n aria-label=\"Close notifications\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n onClick={requestClose}\n >\n <X className=\"h-4 w-4\" />\n </DialogPrimitive.Close>\n </div>\n </header>\n\n <div className=\"flex-1 overflow-y-auto px-4 pb-3\">\n <BrowserNotificationPermissionNotice\n permission={browserNotificationPermission}\n requesting={browserNotificationPermissionRequesting}\n onEnable={requestBrowserNotificationPermission}\n />\n {mergedLoading ? (\n <div className=\"flex items-center justify-center py-10 text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n </div>\n ) : mergedError ? (\n <div className=\"py-10 text-center text-sm text-muted-foreground\">{mergedError}</div>\n ) : visibleItems.length === 0 ? (\n <div className=\"py-10 text-center text-sm text-muted-foreground\">No notifications.</div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div>\n <div className=\"sticky top-0 z-20 -mx-4 flex h-8 items-center border-b bg-background px-4 text-xs font-semibold text-muted-foreground\">\n Unread notifications\n </div>\n {unreadGroups.length === 0 ? (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">No unread notifications.</div>\n ) : (\n unreadGroups.map((group) => (\n <div key={`unread:${group.key}`}>\n <div className=\"sticky top-8 z-10 -mx-4 flex h-7 items-center bg-background px-4 text-[11px] font-semibold text-muted-foreground\">\n {group.label}\n </div>\n <div className=\"-mx-4 flex flex-col divide-y divide-border\">\n {group.items.map((n) => (\n <NotificationRow\n key={n.id}\n notification={n}\n onMarkRead={markAsRead}\n onArchive={archive}\n onAction={requestClose}\n />\n ))}\n </div>\n </div>\n ))\n )}\n </div>\n\n {unreadOnly ? null : (\n <div>\n <div className=\"sticky top-0 z-20 -mx-4 flex h-8 items-center border-b bg-background px-4 text-xs font-semibold text-muted-foreground\">\n Previous notifications\n </div>\n {readGroups.length === 0 ? (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">No previous notifications.</div>\n ) : (\n readGroups.map((group) => (\n <div key={`previous:${group.key}`}>\n <div className=\"sticky top-8 z-10 -mx-4 flex h-7 items-center bg-background px-4 text-[11px] font-semibold text-muted-foreground\">\n {group.label}\n </div>\n <div className=\"-mx-4 flex flex-col divide-y divide-border\">\n {group.items.map((n) => (\n <NotificationRow\n key={n.id}\n notification={n}\n onMarkRead={markAsRead}\n onArchive={archive}\n onAction={requestClose}\n />\n ))}\n </div>\n </div>\n ))\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n )\n}\n\nfunction BrowserNotificationPermissionNotice({\n permission,\n requesting,\n onEnable,\n}: {\n permission: BrowserNotificationPermission\n requesting: boolean\n onEnable: () => void\n}) {\n if (permission === \"granted\") return null\n\n if (permission === \"unsupported\") {\n return (\n <div className=\"mt-3 rounded-md border bg-muted/40 p-3 text-sm text-muted-foreground\">\n Browser notifications are not supported by this browser.\n </div>\n )\n }\n\n const isDenied = permission === \"denied\"\n\n return (\n <div className=\"mt-3 rounded-md border bg-muted/40 p-3\">\n <div className=\"text-sm font-medium\">\n {isDenied ? \"Browser notifications are blocked.\" : \"Browser notifications are not enabled.\"}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {isDenied\n ? \"Enable notifications for this site in your browser settings, then click again.\"\n : \"Enable browser notifications to receive important alerts.\"}\n </div>\n <button\n type=\"button\"\n className=\"mt-3 inline-flex h-8 items-center rounded-md border bg-background px-2.5 text-xs font-medium hover:bg-accent disabled:pointer-events-none disabled:opacity-50\"\n disabled={requesting}\n onClick={onEnable}\n >\n {requesting ? \"Enabling...\" : \"Enable browser notifications\"}\n </button>\n </div>\n )\n}\n\nfunction NotificationRow({\n notification,\n onMarkRead,\n onArchive,\n onAction,\n}: {\n notification: NotificationItem\n onMarkRead: (id: string) => void\n onArchive: (id: string) => void\n onAction: () => void\n}) {\n const [menuOpen, setMenuOpen] = useState(false)\n const menuRef = useRef<HTMLDivElement | null>(null)\n const menuButtonRef = useRef<HTMLButtonElement | null>(null)\n const isUnread = !notification.readAt\n const action = getNotificationAction(notification)\n const actionLabel = getNotificationActionLabel(action)\n const createdAtLabel = (() => {\n const date = new Date(toMs(notification.createdAt))\n if (!Number.isFinite(date.getTime())) return \"\"\n return date.toLocaleString()\n })()\n\n useEffect(() => {\n if (!menuOpen) return\n\n const handlePointerDown = (event: PointerEvent) => {\n const target = event.target\n if (!(target instanceof Node)) return\n if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) return\n setMenuOpen(false)\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return\n setMenuOpen(false)\n menuButtonRef.current?.focus()\n }\n\n document.addEventListener(\"pointerdown\", handlePointerDown)\n document.addEventListener(\"keydown\", handleKeyDown)\n\n return () => {\n document.removeEventListener(\"pointerdown\", handlePointerDown)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [menuOpen])\n\n return (\n <div\n className={cn(\n \"group flex w-full gap-3 px-4 py-3 hover:bg-accent/40\",\n isUnread ? \"bg-primary/5\" : \"bg-background\",\n )}\n >\n <button\n type=\"button\"\n className=\"min-w-0 flex-1 text-left\"\n onClick={() => {\n if (isUnread) {\n void onMarkRead(notification.id)\n }\n if (action) {\n onAction()\n applyNotificationAction(action)\n }\n }}\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold\">{notification.title}</div>\n {notification.body ? (\n <div className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\">{notification.body}</div>\n ) : null}\n </div>\n {actionLabel ? <div className=\"mt-2 truncate text-xs text-primary\">{actionLabel}</div> : null}\n {createdAtLabel ? (\n <div className=\"mt-2 text-[11px] text-muted-foreground\">{createdAtLabel}</div>\n ) : null}\n </button>\n <div className=\"relative flex items-start\">\n <button\n ref={menuButtonRef}\n type=\"button\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n aria-label=\"Notification options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => {\n setMenuOpen((open) => !open)\n }}\n >\n <MoreVertical className=\"h-4 w-4\" />\n </button>\n {menuOpen ? (\n <div\n ref={menuRef}\n role=\"menu\"\n className={cn(\n \"absolute right-0 top-full z-50 mt-1 min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n \"animate-in fade-in-0 zoom-in-95\",\n )}\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground\"\n onClick={() => {\n setMenuOpen(false)\n onArchive(notification.id)\n }}\n >\n Archive\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n disabled={!isUnread}\n onClick={() => {\n setMenuOpen(false)\n onMarkRead(notification.id)\n }}\n >\n Mark as read\n </button>\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index420.js","names":["{\n permission,\n requesting,\n onEnable,\n}: {\n permission: BrowserNotificationPermission\n requesting: boolean\n onEnable: () => void\n}","<div className=\"mt-3 rounded-md border bg-muted/40 p-3 text-sm text-muted-foreground\">\n Browser notifications are not supported by this browser.\n </div>","<div className=\"text-sm font-medium\">\n {isDenied ? \"Browser notifications are blocked.\" : \"Browser notifications are not enabled.\"}\n </div>","isDenied ? \"Browser notifications are blocked.\" : \"Browser notifications are not enabled.\"","<div className=\"mt-1 text-xs text-muted-foreground\">\n {isDenied\n ? \"Enable notifications for this site in your browser settings, then click again.\"\n : \"Enable browser notifications to receive important alerts.\"}\n </div>","isDenied\n ? \"Enable notifications for this site in your browser settings, then click again.\"\n : \"Enable browser notifications to receive important alerts.\"","<button\n type=\"button\"\n className=\"mt-3 inline-flex h-8 items-center rounded-md border bg-background px-2.5 text-xs font-medium hover:bg-accent disabled:pointer-events-none disabled:opacity-50\"\n disabled={requesting}\n onClick={onEnable}\n >\n {requesting ? \"Enabling...\" : \"Enable browser notifications\"}\n </button>","requesting ? \"Enabling...\" : \"Enable browser notifications\"","<div className=\"mt-3 rounded-md border bg-muted/40 p-3\">\n <div className=\"text-sm font-medium\">\n {isDenied ? \"Browser notifications are blocked.\" : \"Browser notifications are not enabled.\"}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {isDenied\n ? \"Enable notifications for this site in your browser settings, then click again.\"\n : \"Enable browser notifications to receive important alerts.\"}\n </div>\n <button\n type=\"button\"\n className=\"mt-3 inline-flex h-8 items-center rounded-md border bg-background px-2.5 text-xs font-medium hover:bg-accent disabled:pointer-events-none disabled:opacity-50\"\n disabled={requesting}\n onClick={onEnable}\n >\n {requesting ? \"Enabling...\" : \"Enable browser notifications\"}\n </button>\n </div>","{\n notification,\n onMarkRead,\n onArchive,\n onAction,\n}: {\n notification: NotificationItem\n onMarkRead: (id: string) => void\n onArchive: (id: string) => void\n onAction: () => void\n}","getNotificationActionLabel(action)","(() => {\n const date = new Date(toMs(notification.createdAt))\n if (!Number.isFinite(date.getTime())) return \"\"\n return date.toLocaleString()\n })()","createdAt","() => {\n if (!menuOpen) return\n\n const handlePointerDown = (event: PointerEvent) => {\n const target = event.target\n if (!(target instanceof Node)) return\n if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) return\n setMenuOpen(false)\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return\n setMenuOpen(false)\n menuButtonRef.current?.focus()\n }\n\n document.addEventListener(\"pointerdown\", handlePointerDown)\n document.addEventListener(\"keydown\", handleKeyDown)\n\n return () => {\n document.removeEventListener(\"pointerdown\", handlePointerDown)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }","[menuOpen]","event","cn(\n \"group flex w-full gap-3 px-4 py-3 hover:bg-accent/40\",\n isUnread ? \"bg-primary/5\" : \"bg-background\",\n )","isUnread ? \"bg-primary/5\" : \"bg-background\"","() => {\n if (isUnread) {\n void onMarkRead(notification.id)\n }\n if (action) {\n onAction()\n applyNotificationAction(action)\n }\n }","id","<div className=\"truncate text-sm font-semibold\">{notification.title}</div>","title","notification.body ? (\n <div className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\">{notification.body}</div>\n ) : null","body","<div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold\">{notification.title}</div>\n {notification.body ? (\n <div className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\">{notification.body}</div>\n ) : null}\n </div>","actionLabel ? <div className=\"mt-2 truncate text-xs text-primary\">{actionLabel}</div> : null","createdAtLabel ? (\n <div className=\"mt-2 text-[11px] text-muted-foreground\">{createdAtLabel}</div>\n ) : null","<button\n type=\"button\"\n className=\"min-w-0 flex-1 text-left\"\n onClick={() => {\n if (isUnread) {\n void onMarkRead(notification.id)\n }\n if (action) {\n onAction()\n applyNotificationAction(action)\n }\n }}\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold\">{notification.title}</div>\n {notification.body ? (\n <div className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\">{notification.body}</div>\n ) : null}\n </div>\n {actionLabel ? <div className=\"mt-2 truncate text-xs text-primary\">{actionLabel}</div> : null}\n {createdAtLabel ? (\n <div className=\"mt-2 text-[11px] text-muted-foreground\">{createdAtLabel}</div>\n ) : null}\n </button>","() => {\n setMenuOpen((open) => !open)\n }","<MoreVertical className=\"h-4 w-4\" />","(open) => !open","<button\n ref={menuButtonRef}\n type=\"button\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n aria-label=\"Notification options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => {\n setMenuOpen((open) => !open)\n }}\n >\n <MoreVertical className=\"h-4 w-4\" />\n </button>","menuOpen ? (\n <div\n ref={menuRef}\n role=\"menu\"\n className={cn(\n \"absolute right-0 top-full z-50 mt-1 min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n \"animate-in fade-in-0 zoom-in-95\",\n )}\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground\"\n onClick={() => {\n setMenuOpen(false)\n onArchive(notification.id)\n }}\n >\n Archive\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n disabled={!isUnread}\n onClick={() => {\n setMenuOpen(false)\n onMarkRead(notification.id)\n }}\n >\n Mark as read\n </button>\n </div>\n ) : null","<div className=\"relative flex items-start\">\n <button\n ref={menuButtonRef}\n type=\"button\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n aria-label=\"Notification options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => {\n setMenuOpen((open) => !open)\n }}\n >\n <MoreVertical className=\"h-4 w-4\" />\n </button>\n {menuOpen ? (\n <div\n ref={menuRef}\n role=\"menu\"\n className={cn(\n \"absolute right-0 top-full z-50 mt-1 min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n \"animate-in fade-in-0 zoom-in-95\",\n )}\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground\"\n onClick={() => {\n setMenuOpen(false)\n onArchive(notification.id)\n }}\n >\n Archive\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n disabled={!isUnread}\n onClick={() => {\n setMenuOpen(false)\n onMarkRead(notification.id)\n }}\n >\n Mark as read\n </button>\n </div>\n ) : null}\n </div>","<div\n className={cn(\n \"group flex w-full gap-3 px-4 py-3 hover:bg-accent/40\",\n isUnread ? \"bg-primary/5\" : \"bg-background\",\n )}\n >\n <button\n type=\"button\"\n className=\"min-w-0 flex-1 text-left\"\n onClick={() => {\n if (isUnread) {\n void onMarkRead(notification.id)\n }\n if (action) {\n onAction()\n applyNotificationAction(action)\n }\n }}\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold\">{notification.title}</div>\n {notification.body ? (\n <div className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\">{notification.body}</div>\n ) : null}\n </div>\n {actionLabel ? <div className=\"mt-2 truncate text-xs text-primary\">{actionLabel}</div> : null}\n {createdAtLabel ? (\n <div className=\"mt-2 text-[11px] text-muted-foreground\">{createdAtLabel}</div>\n ) : null}\n </button>\n <div className=\"relative flex items-start\">\n <button\n ref={menuButtonRef}\n type=\"button\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n aria-label=\"Notification options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => {\n setMenuOpen((open) => !open)\n }}\n >\n <MoreVertical className=\"h-4 w-4\" />\n </button>\n {menuOpen ? (\n <div\n ref={menuRef}\n role=\"menu\"\n className={cn(\n \"absolute right-0 top-full z-50 mt-1 min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n \"animate-in fade-in-0 zoom-in-95\",\n )}\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground\"\n onClick={() => {\n setMenuOpen(false)\n onArchive(notification.id)\n }}\n >\n Archive\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n disabled={!isUnread}\n onClick={() => {\n setMenuOpen(false)\n onMarkRead(notification.id)\n }}\n >\n Mark as read\n </button>\n </div>\n ) : null}\n </div>\n </div>"],"sources":["../src/components/Notifications/NotificationsDrawer.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type Context } from \"react\"\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\"\nimport { Loader2, MoreVertical, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport type { NotificationItem, NotificationsRealtimeState } from \"./types\"\n\n\ntype ListResponse = {\n ok?: boolean\n error?: string\n notifications?: NotificationItem[]\n unreadCount?: number\n unseenCount?: number\n}\n\ntype DayGroup = {\n key: string\n label: string\n items: NotificationItem[]\n}\n\ntype BrowserNotificationPermission = NotificationPermission | \"unsupported\"\ntype NotificationActionMode = \"navigate\" | \"merge_current_search\"\n\ntype NotificationAction = {\n link: string\n mode: NotificationActionMode\n}\n\nconst toMs = (iso: string): number => {\n const ms = Date.parse(iso)\n return Number.isFinite(ms) ? ms : 0\n}\n\nconst getDayKey = (iso: string): string => {\n const date = new Date(toMs(iso))\n if (!Number.isFinite(date.getTime())) return \"unknown\"\n\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, \"0\")\n const day = String(date.getDate()).padStart(2, \"0\")\n\n return `${year}-${month}-${day}`\n}\n\nconst formatDayLabel = (key: string): string => {\n if (key === \"unknown\") return \"Unknown date\"\n const [yearRaw, monthRaw, dayRaw] = key.split(\"-\")\n const year = Number(yearRaw)\n const month = Number(monthRaw)\n const day = Number(dayRaw)\n\n if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return \"Unknown date\"\n\n const date = new Date(year, month - 1, day)\n if (!Number.isFinite(date.getTime())) return \"Unknown date\"\n\n const now = new Date()\n const includeYear = now.getFullYear() !== year\n return date.toLocaleDateString(undefined, includeYear\n ? { weekday: \"short\", month: \"short\", day: \"numeric\", year: \"numeric\" }\n : { weekday: \"short\", month: \"short\", day: \"numeric\" })\n}\n\nconst getNotificationLink = (item: NotificationItem): string => {\n const dataLink = item.data?.link?.trim()\n if (dataLink) return dataLink\n\n const platformWebpush = item.platform?.webpush\n if (!platformWebpush || typeof platformWebpush !== \"object\" || Array.isArray(platformWebpush)) return \"\"\n const fcmOptions = (platformWebpush as { fcmOptions?: unknown }).fcmOptions\n if (!fcmOptions || typeof fcmOptions !== \"object\" || Array.isArray(fcmOptions)) return \"\"\n const link = (fcmOptions as { link?: unknown }).link\n return typeof link === \"string\" ? link.trim() : \"\"\n}\n\nconst getNotificationActionMode = (item: NotificationItem): NotificationActionMode => {\n return item.data?.linkMode === \"merge_current_search\" ? \"merge_current_search\" : \"navigate\"\n}\n\nconst getNotificationAction = (item: NotificationItem): NotificationAction | null => {\n const link = getNotificationLink(item)\n if (!link) return null\n return {\n link,\n mode: getNotificationActionMode(item),\n }\n}\n\nconst getNotificationActionLabel = (action: NotificationAction | null): string => {\n if (!action) return \"\"\n return action.mode === \"merge_current_search\" ? \"Open\" : action.link\n}\n\nconst pushUrlWithoutReload = (url: string): void => {\n window.history.pushState(window.history.state, \"\", url)\n window.dispatchEvent(new PopStateEvent(\"popstate\", { state: window.history.state }))\n}\n\nconst applyNotificationAction = (action: NotificationAction): void => {\n if (action.mode === \"merge_current_search\") {\n const currentUrl = new URL(window.location.href)\n const actionUrl = new URL(action.link, currentUrl)\n\n actionUrl.searchParams.forEach((value, key) => {\n currentUrl.searchParams.set(key, value)\n })\n if (actionUrl.hash) {\n currentUrl.hash = actionUrl.hash\n }\n\n pushUrlWithoutReload(`${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`)\n return\n }\n\n window.location.href = action.link\n}\n\nconst groupByDay = (items: NotificationItem[]): DayGroup[] => {\n const groups: DayGroup[] = []\n let currentKey: string | null = null\n\n for (const item of items) {\n const key = getDayKey(item.createdAt)\n if (key !== currentKey) {\n currentKey = key\n groups.push({ key, label: formatDayLabel(key), items: [] })\n }\n groups[groups.length - 1]!.items.push(item)\n }\n\n return groups\n}\n\nconst getBrowserNotificationPermission = (): BrowserNotificationPermission => {\n if (typeof window === \"undefined\") return \"unsupported\"\n if (!(\"Notification\" in window)) return \"unsupported\"\n return window.Notification.permission\n}\n\nconst EmptyRealtimeContext = createContext<NotificationsRealtimeState | null>(null)\n\nexport function NotificationsDrawer({\n open,\n onOpenChange,\n className,\n limit = 50,\n defaultUnreadOnly = false,\n realtimeContext,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n className?: string\n limit?: number\n defaultUnreadOnly?: boolean\n realtimeContext?: Context<NotificationsRealtimeState | null>\n}) {\n const realtime = useContext(realtimeContext ?? EmptyRealtimeContext)\n\n const [items, setItems] = useState<NotificationItem[]>([])\n const [unreadCount, setUnreadCount] = useState<number>(0)\n const [unseenCount, setUnseenCount] = useState<number>(0)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [unreadOnly, setUnreadOnly] = useState<boolean>(defaultUnreadOnly)\n const [browserNotificationPermission, setBrowserNotificationPermission] = useState<BrowserNotificationPermission>(() =>\n getBrowserNotificationPermission(),\n )\n const [browserNotificationPermissionRequesting, setBrowserNotificationPermissionRequesting] = useState(false)\n\n const mergedItems = realtime?.notifications ?? items\n const mergedUnreadCount = realtime?.unreadCount ?? unreadCount\n const mergedUnseenCount = realtime?.unseenCount ?? unseenCount\n const mergedLoading = realtime?.loading ?? loading\n const mergedError = realtime?.error\n ? (realtime.error instanceof Error ? realtime.error.message : String(realtime.error))\n : error\n\n const visibleItems = useMemo(() => mergedItems.slice(0, limit), [limit, mergedItems])\n\n const unread = useMemo(() => visibleItems.filter((n) => !n.readAt), [visibleItems])\n const read = useMemo(() => visibleItems.filter((n) => Boolean(n.readAt)), [visibleItems])\n const unreadGroups = useMemo(() => groupByDay(unread), [unread])\n const readGroups = useMemo(() => groupByDay(read), [read])\n\n const refresh = useCallback(async ({ unreadOnly, markSeen }: { unreadOnly: boolean; markSeen: boolean }) => {\n setLoading(true)\n setError(null)\n\n try {\n const res = await fetch(\"/api/rb/notifications\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify({ limit, unreadOnly, markSeen }),\n })\n\n const json = (await res.json().catch(() => null)) as ListResponse | null\n if (!res.ok || !json?.ok) {\n setError(json?.error || \"Unable to load notifications.\")\n return\n }\n\n const nextItems = Array.isArray(json.notifications) ? json.notifications : []\n setItems(nextItems)\n setUnreadCount(Number.isFinite(json.unreadCount) ? Math.max(0, Math.floor(json.unreadCount ?? 0)) : 0)\n setUnseenCount(Number.isFinite(json.unseenCount) ? Math.max(0, Math.floor(json.unseenCount ?? 0)) : 0)\n } catch {\n setError(\"Unable to load notifications.\")\n } finally {\n setLoading(false)\n }\n }, [limit])\n\n useEffect(() => {\n if (!open) return\n if (realtime) {\n void (async () => {\n try {\n await fetch(\"/api/rb/notifications\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n keepalive: true,\n body: JSON.stringify({ limit: 1, markSeen: true }),\n })\n } catch {\n return\n }\n })()\n return\n }\n void refresh({ unreadOnly, markSeen: true })\n }, [open, realtime, refresh, unreadOnly])\n\n useEffect(() => {\n if (!open) return\n if (realtime) return\n void refresh({ unreadOnly, markSeen: false })\n }, [open, realtime, refresh, unreadOnly])\n\n useEffect(() => {\n if (!open) return\n\n setBrowserNotificationPermission(getBrowserNotificationPermission())\n\n if (typeof navigator === \"undefined\" || !navigator.permissions?.query) return\n\n let permissionStatus: PermissionStatus | null = null\n let disposed = false\n const syncPermission = () => {\n setBrowserNotificationPermission(getBrowserNotificationPermission())\n }\n\n try {\n void navigator.permissions.query({ name: \"notifications\" as PermissionName }).then((status) => {\n if (disposed) return\n permissionStatus = status\n syncPermission()\n status.addEventListener(\"change\", syncPermission)\n }).catch(() => {\n // Notifications are not exposed through the Permissions API in every browser.\n })\n } catch {\n return\n }\n\n return () => {\n disposed = true\n permissionStatus?.removeEventListener(\"change\", syncPermission)\n }\n }, [open])\n\n const requestBrowserNotificationPermission = async () => {\n if (typeof window === \"undefined\" || !(\"Notification\" in window)) {\n setBrowserNotificationPermission(\"unsupported\")\n return\n }\n\n setBrowserNotificationPermissionRequesting(true)\n try {\n const nextPermission = await window.Notification.requestPermission()\n setBrowserNotificationPermission(nextPermission)\n } catch {\n setBrowserNotificationPermission(getBrowserNotificationPermission())\n } finally {\n setBrowserNotificationPermissionRequesting(false)\n }\n }\n\n const markAsRead = async (id: string) => {\n try {\n const res = await fetch(`/api/rb/notifications/${encodeURIComponent(id)}/read`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n keepalive: true,\n body: \"{}\",\n })\n\n const json = (await res.json().catch(() => null)) as { ok?: boolean } | null\n if (!res.ok || !json?.ok) return\n\n const nowIso = new Date().toISOString()\n if (realtime) return\n setItems((current) => {\n const target = current.find((n) => n.id === id)\n if (target && !target.readAt) {\n setUnreadCount((count) => Math.max(0, count - 1))\n }\n if (target && !target.seenAt) {\n setUnseenCount((count) => Math.max(0, count - 1))\n }\n return current.map((n) => (n.id === id && !n.readAt ? { ...n, readAt: nowIso, ...(n.seenAt ? {} : { seenAt: nowIso }) } : n))\n })\n } catch {\n return\n }\n }\n\n const archive = async (id: string) => {\n try {\n const res = await fetch(`/api/rb/notifications/${encodeURIComponent(id)}/archive`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: \"{}\",\n })\n\n const json = (await res.json().catch(() => null)) as { ok?: boolean } | null\n if (!res.ok || !json?.ok) return\n\n if (realtime) return\n setItems((current) => {\n const target = current.find((n) => n.id === id)\n if (target && !target.readAt) {\n setUnreadCount((count) => Math.max(0, count - 1))\n }\n if (target && !target.seenAt) {\n setUnseenCount((count) => Math.max(0, count - 1))\n }\n return current.filter((n) => n.id !== id)\n })\n } catch {\n return\n }\n }\n\n const requestClose = () => {\n onOpenChange(false)\n }\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (nextOpen) {\n onOpenChange(true)\n return\n }\n\n requestClose()\n }\n\n return (\n <DialogPrimitive.Root open={open} onOpenChange={handleOpenChange}>\n <DialogPrimitive.Portal>\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black/50\" onClick={requestClose} />\n <DialogPrimitive.Popup\n className={cn(\n \"fixed right-0 top-0 z-50 flex h-full w-full max-w-md flex-col border-l bg-background shadow-xl outline-none\",\n className,\n )}\n >\n <header className=\"flex items-center justify-between gap-2 border-b px-4 py-3\">\n <div className=\"flex min-w-0 flex-col\">\n <DialogPrimitive.Title className=\"text-sm font-semibold\">Notifications</DialogPrimitive.Title>\n <span className=\"text-xs text-muted-foreground\">\n {mergedUnreadCount > 0 ? `${mergedUnreadCount} unread` : mergedUnseenCount > 0 ? `${mergedUnseenCount} new` : \"All caught up\"}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setUnreadOnly((v) => !v)}\n className={cn(\n \"rounded-md border px-2 py-1 text-xs\",\n unreadOnly ? \"bg-accent text-foreground\" : \"bg-background text-muted-foreground hover:bg-accent\",\n )}\n >\n {unreadOnly ? \"Unread\" : \"All\"}\n </button>\n <DialogPrimitive.Close\n type=\"button\"\n aria-label=\"Close notifications\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n onClick={requestClose}\n >\n <X className=\"h-4 w-4\" />\n </DialogPrimitive.Close>\n </div>\n </header>\n\n <div className=\"flex-1 overflow-y-auto px-4 pb-3\">\n <BrowserNotificationPermissionNotice\n permission={browserNotificationPermission}\n requesting={browserNotificationPermissionRequesting}\n onEnable={requestBrowserNotificationPermission}\n />\n {mergedLoading ? (\n <div className=\"flex items-center justify-center py-10 text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n </div>\n ) : mergedError ? (\n <div className=\"py-10 text-center text-sm text-muted-foreground\">{mergedError}</div>\n ) : visibleItems.length === 0 ? (\n <div className=\"py-10 text-center text-sm text-muted-foreground\">No notifications.</div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div>\n <div className=\"sticky top-0 z-20 -mx-4 flex h-8 items-center border-b bg-background px-4 text-xs font-semibold text-muted-foreground\">\n Unread notifications\n </div>\n {unreadGroups.length === 0 ? (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">No unread notifications.</div>\n ) : (\n unreadGroups.map((group) => (\n <div key={`unread:${group.key}`}>\n <div className=\"sticky top-8 z-10 -mx-4 flex h-7 items-center bg-background px-4 text-[11px] font-semibold text-muted-foreground\">\n {group.label}\n </div>\n <div className=\"-mx-4 flex flex-col divide-y divide-border\">\n {group.items.map((n) => (\n <NotificationRow\n key={n.id}\n notification={n}\n onMarkRead={markAsRead}\n onArchive={archive}\n onAction={requestClose}\n />\n ))}\n </div>\n </div>\n ))\n )}\n </div>\n\n {unreadOnly ? null : (\n <div>\n <div className=\"sticky top-0 z-20 -mx-4 flex h-8 items-center border-b bg-background px-4 text-xs font-semibold text-muted-foreground\">\n Previous notifications\n </div>\n {readGroups.length === 0 ? (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">No previous notifications.</div>\n ) : (\n readGroups.map((group) => (\n <div key={`previous:${group.key}`}>\n <div className=\"sticky top-8 z-10 -mx-4 flex h-7 items-center bg-background px-4 text-[11px] font-semibold text-muted-foreground\">\n {group.label}\n </div>\n <div className=\"-mx-4 flex flex-col divide-y divide-border\">\n {group.items.map((n) => (\n <NotificationRow\n key={n.id}\n notification={n}\n onMarkRead={markAsRead}\n onArchive={archive}\n onAction={requestClose}\n />\n ))}\n </div>\n </div>\n ))\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n )\n}\n\nfunction BrowserNotificationPermissionNotice({\n permission,\n requesting,\n onEnable,\n}: {\n permission: BrowserNotificationPermission\n requesting: boolean\n onEnable: () => void\n}) {\n if (permission === \"granted\") return null\n\n if (permission === \"unsupported\") {\n return (\n <div className=\"mt-3 rounded-md border bg-muted/40 p-3 text-sm text-muted-foreground\">\n Browser notifications are not supported by this browser.\n </div>\n )\n }\n\n const isDenied = permission === \"denied\"\n\n return (\n <div className=\"mt-3 rounded-md border bg-muted/40 p-3\">\n <div className=\"text-sm font-medium\">\n {isDenied ? \"Browser notifications are blocked.\" : \"Browser notifications are not enabled.\"}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {isDenied\n ? \"Enable notifications for this site in your browser settings, then click again.\"\n : \"Enable browser notifications to receive important alerts.\"}\n </div>\n <button\n type=\"button\"\n className=\"mt-3 inline-flex h-8 items-center rounded-md border bg-background px-2.5 text-xs font-medium hover:bg-accent disabled:pointer-events-none disabled:opacity-50\"\n disabled={requesting}\n onClick={onEnable}\n >\n {requesting ? \"Enabling...\" : \"Enable browser notifications\"}\n </button>\n </div>\n )\n}\n\nfunction NotificationRow({\n notification,\n onMarkRead,\n onArchive,\n onAction,\n}: {\n notification: NotificationItem\n onMarkRead: (id: string) => void\n onArchive: (id: string) => void\n onAction: () => void\n}) {\n const [menuOpen, setMenuOpen] = useState(false)\n const menuRef = useRef<HTMLDivElement | null>(null)\n const menuButtonRef = useRef<HTMLButtonElement | null>(null)\n const isUnread = !notification.readAt\n const action = getNotificationAction(notification)\n const actionLabel = getNotificationActionLabel(action)\n const createdAtLabel = (() => {\n const date = new Date(toMs(notification.createdAt))\n if (!Number.isFinite(date.getTime())) return \"\"\n return date.toLocaleString()\n })()\n\n useEffect(() => {\n if (!menuOpen) return\n\n const handlePointerDown = (event: PointerEvent) => {\n const target = event.target\n if (!(target instanceof Node)) return\n if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) return\n setMenuOpen(false)\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return\n setMenuOpen(false)\n menuButtonRef.current?.focus()\n }\n\n document.addEventListener(\"pointerdown\", handlePointerDown)\n document.addEventListener(\"keydown\", handleKeyDown)\n\n return () => {\n document.removeEventListener(\"pointerdown\", handlePointerDown)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [menuOpen])\n\n return (\n <div\n className={cn(\n \"group flex w-full gap-3 px-4 py-3 hover:bg-accent/40\",\n isUnread ? \"bg-primary/5\" : \"bg-background\",\n )}\n >\n <button\n type=\"button\"\n className=\"min-w-0 flex-1 text-left\"\n onClick={() => {\n if (isUnread) {\n void onMarkRead(notification.id)\n }\n if (action) {\n onAction()\n applyNotificationAction(action)\n }\n }}\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold\">{notification.title}</div>\n {notification.body ? (\n <div className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\">{notification.body}</div>\n ) : null}\n </div>\n {actionLabel ? <div className=\"mt-2 truncate text-xs text-primary\">{actionLabel}</div> : null}\n {createdAtLabel ? (\n <div className=\"mt-2 text-[11px] text-muted-foreground\">{createdAtLabel}</div>\n ) : null}\n </button>\n <div className=\"relative flex items-start\">\n <button\n ref={menuButtonRef}\n type=\"button\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent\"\n aria-label=\"Notification options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => {\n setMenuOpen((open) => !open)\n }}\n >\n <MoreVertical className=\"h-4 w-4\" />\n </button>\n {menuOpen ? (\n <div\n ref={menuRef}\n role=\"menu\"\n className={cn(\n \"absolute right-0 top-full z-50 mt-1 min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n \"animate-in fade-in-0 zoom-in-95\",\n )}\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground\"\n onClick={() => {\n setMenuOpen(false)\n onArchive(notification.id)\n }}\n >\n Archive\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n disabled={!isUnread}\n onClick={() => {\n setMenuOpen(false)\n onMarkRead(notification.id)\n }}\n >\n Mark as read\n </button>\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;AA+BA,IAAM,KAAQ,MAAwB;CACpC,IAAM,IAAK,KAAK,MAAM,CAAG;CACzB,OAAO,OAAO,SAAS,CAAE,IAAI,IAAK;AACpC,GAEM,KAAa,MAAwB;CACzC,IAAM,IAAO,IAAI,KAAK,EAAK,CAAG,CAAC;CAO/B,OANK,OAAO,SAAS,EAAK,QAAQ,CAAC,IAM5B,GAJM,EAAK,YAIR,EAAK,GAHD,OAAO,EAAK,SAAS,IAAI,CAAC,CAAC,CAAC,SAAS,GAAG,GAGpC,EAAM,GAFZ,OAAO,EAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,GAAG,GAEpB,MANkB;AAO/C,GAEM,KAAkB,MAAwB;CAC9C,IAAI,MAAQ,WAAW,OAAO;CAC9B,IAAM,CAAC,GAAS,GAAU,KAAU,EAAI,MAAM,GAAG,GAC3C,IAAO,OAAO,CAAO,GACrB,IAAQ,OAAO,CAAQ,GACvB,IAAM,OAAO,CAAM;CAEzB,IAAI,CAAC,OAAO,SAAS,CAAI,KAAK,CAAC,OAAO,SAAS,CAAK,KAAK,CAAC,OAAO,SAAS,CAAG,GAAG,OAAO;CAEvF,IAAM,IAAO,IAAI,KAAK,GAAM,IAAQ,GAAG,CAAG;CAC1C,IAAI,CAAC,OAAO,SAAS,EAAK,QAAQ,CAAC,GAAG,OAAO;CAG7C,IAAM,qBAAc,IADJ,KACI,EAAA,CAAI,YAAY,MAAM;CAC1C,OAAO,EAAK,mBAAmB,KAAA,GAAW,IACtC;EAAE,SAAS;EAAS,OAAO;EAAS,KAAK;EAAW,MAAM;CAAU,IACpE;EAAE,SAAS;EAAS,OAAO;EAAS,KAAK;CAAU,CAAC;AAC1D,GAEM,KAAuB,MAAmC;CAC9D,IAAM,IAAW,EAAK,MAAM,MAAM,KAAK;CACvC,IAAI,GAAU,OAAO;CAErB,IAAM,IAAkB,EAAK,UAAU;CACvC,IAAI,CAAC,KAAmB,OAAO,KAAoB,YAAY,MAAM,QAAQ,CAAe,GAAG,OAAO;CACtG,IAAM,IAAc,EAA6C;CACjE,IAAI,CAAC,KAAc,OAAO,KAAe,YAAY,MAAM,QAAQ,CAAU,GAAG,OAAO;CACvF,IAAM,IAAQ,EAAkC;CAChD,OAAO,OAAO,KAAS,WAAW,EAAK,KAAK,IAAI;AAClD,GAEM,KAA6B,MAC1B,EAAK,MAAM,aAAa,yBAAyB,yBAAyB,YAG7E,KAAyB,MAAsD;CACnF,IAAM,IAAO,EAAoB,CAAI;CAErC,OADK,IACE;EACL;EACA,MAAM,EAA0B,CAAI;CACtC,IAJkB;AAKpB,GAEM,KAA8B,MAC7B,IACE,EAAO,SAAS,yBAAyB,SAAS,EAAO,OAD5C,IAIhB,KAAwB,MAAsB;CAElD,AADA,OAAO,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI,CAAG,GACtD,OAAO,cAAc,IAAI,cAAc,YAAY,EAAE,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC;AACrF,GAEM,KAA2B,MAAqC;CACpE,IAAI,EAAO,SAAS,wBAAwB;EAC1C,IAAM,IAAa,IAAI,IAAI,OAAO,SAAS,IAAI,GACzC,IAAY,IAAI,IAAI,EAAO,MAAM,CAAU;EASjD,AAPA,EAAU,aAAa,SAAS,GAAO,MAAQ;GAC7C,EAAW,aAAa,IAAI,GAAK,CAAK;EACxC,CAAC,GACG,EAAU,SACZ,EAAW,OAAO,EAAU,OAG9B,EAAqB,GAAG,EAAW,WAAW,EAAW,SAAS,EAAW,MAAM;EACnF;CACF;CAEA,OAAO,SAAS,OAAO,EAAO;AAChC,GAEM,KAAc,MAA0C;CAC5D,IAAM,IAAqB,CAAC,GACxB,IAA4B;CAEhC,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAM,IAAM,EAAU,EAAK,SAAS;EAKpC,AAJI,MAAQ,MACV,IAAa,GACb,EAAO,KAAK;GAAE;GAAK,OAAO,EAAe,CAAG;GAAG,OAAO,CAAC;EAAE,CAAC,IAE5D,EAAO,EAAO,SAAS,EAAE,CAAE,MAAM,KAAK,CAAI;CAC5C;CAEA,OAAO;AACT,GAEM,UACA,OAAO,SAAW,OAClB,EAAE,kBAAkB,UAAgB,gBACjC,OAAO,aAAa,YAGvB,IAAuB,EAAiD,IAAI;AAElF,SAAgB,EAAoB,EAClC,SACA,iBACA,cACA,WAAQ,IACR,uBAAoB,IACpB,sBAQC;CACD,IAAM,IAAW,EAAW,KAAmB,CAAoB,GAE7D,CAAC,GAAO,KAAY,EAA6B,CAAC,CAAC,GACnD,CAAC,GAAa,KAAkB,EAAiB,CAAC,GAClD,CAAC,GAAa,KAAkB,EAAiB,CAAC,GAClD,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAO,KAAY,EAAwB,IAAI,GAChD,CAAC,GAAY,KAAiB,EAAkB,CAAiB,GACjE,CAAC,GAA+B,KAAoC,QACxE,EAAiC,CACnC,GACM,CAAC,GAAyC,KAA8C,EAAS,EAAK,GAEtG,IAAc,GAAU,iBAAiB,GACzC,IAAoB,GAAU,eAAe,GAC7C,IAAoB,GAAU,eAAe,GAC7C,KAAgB,GAAU,WAAW,GACrC,IAAc,GAAU,QACzB,EAAS,iBAAiB,QAAQ,EAAS,MAAM,UAAU,OAAO,EAAS,KAAK,IACjF,GAEE,IAAe,QAAc,EAAY,MAAM,GAAG,CAAK,GAAG,CAAC,GAAO,CAAW,CAAC,GAE9E,IAAS,QAAc,EAAa,QAAQ,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAAY,CAAC,GAC5E,IAAO,QAAc,EAAa,QAAQ,MAAM,EAAQ,EAAE,MAAO,GAAG,CAAC,CAAY,CAAC,GAClF,IAAe,QAAc,EAAW,CAAM,GAAG,CAAC,CAAM,CAAC,GACzD,KAAa,QAAc,EAAW,CAAI,GAAG,CAAC,CAAI,CAAC,GAEnD,IAAU,EAAY,OAAO,EAAE,eAAY,kBAA2D;EAE1G,AADA,EAAW,EAAI,GACf,EAAS,IAAI;EAEb,IAAI;GACF,IAAM,IAAM,MAAM,MAAM,yBAAyB;IAC/C,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,aAAa;IACb,MAAM,KAAK,UAAU;KAAE;KAAO;KAAY;IAAS,CAAC;GACtD,CAAC,GAEK,IAAQ,MAAM,EAAI,KAAK,CAAC,CAAC,YAAY,IAAI;GAC/C,IAAI,CAAC,EAAI,MAAM,CAAC,GAAM,IAAI;IACxB,EAAS,GAAM,SAAS,+BAA+B;IACvD;GACF;GAEA,IAAM,IAAY,MAAM,QAAQ,EAAK,aAAa,IAAI,EAAK,gBAAgB,CAAC;GAG5E,AAFA,EAAS,CAAS,GAClB,EAAe,OAAO,SAAS,EAAK,WAAW,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GACrG,EAAe,OAAO,SAAS,EAAK,WAAW,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAK,eAAe,CAAC,CAAC,IAAI,CAAC;EACvG,QAAQ;GACN,EAAS,+BAA+B;EAC1C,UAAU;GACR,EAAW,EAAK;EAClB;CACF,GAAG,CAAC,CAAK,CAAC;CA6BV,AA3BA,QAAgB;EACT,OACL;OAAI,GAAU;IACZ,CAAM,YAAY;KAChB,IAAI;MACF,MAAM,MAAM,yBAAyB;OACnC,QAAQ;OACR,SAAS,EAAE,gBAAgB,mBAAmB;OAC9C,aAAa;OACb,WAAW;OACX,MAAM,KAAK,UAAU;QAAE,OAAO;QAAG,UAAU;OAAK,CAAC;MACnD,CAAC;KACH,QAAQ;MACN;KACF;IACF,EAAA,CAAG;IACH;GACF;GACA,EAAa;IAAE;IAAY,UAAU;GAAK,CAAC;EAD3C;CAEF,GAAG;EAAC;EAAM;EAAU;EAAS;CAAU,CAAC,GAExC,QAAgB;EACT,MACD,KACJ,EAAa;GAAE;GAAY,UAAU;EAAM,CAAC;CAC9C,GAAG;EAAC;EAAM;EAAU;EAAS;CAAU,CAAC,GAExC,QAAgB;EAKd,IAJI,CAAC,MAEL,EAAiC,EAAiC,CAAC,GAE/D,OAAO,YAAc,OAAe,CAAC,UAAU,aAAa,QAAO;EAEvE,IAAI,IAA4C,MAC5C,IAAW,IACT,UAAuB;GAC3B,EAAiC,EAAiC,CAAC;EACrE;EAEA,IAAI;GACF,UAAe,YAAY,MAAM,EAAE,MAAM,gBAAkC,CAAC,CAAC,CAAC,MAAM,MAAW;IACzF,MACJ,IAAmB,GACnB,EAAe,GACf,EAAO,iBAAiB,UAAU,CAAc;GAClD,CAAC,CAAC,CAAC,YAAY,CAEf,CAAC;EACH,QAAQ;GACN;EACF;EAEA,aAAa;GAEX,AADA,IAAW,IACX,GAAkB,oBAAoB,UAAU,CAAc;EAChE;CACF,GAAG,CAAC,CAAI,CAAC;CAET,IAAM,KAAuC,YAAY;EACvD,IAAI,OAAO,SAAW,OAAe,EAAE,kBAAkB,SAAS;GAChE,EAAiC,aAAa;GAC9C;EACF;EAEA,EAA2C,EAAI;EAC/C,IAAI;GACF,IAAM,IAAiB,MAAM,OAAO,aAAa,kBAAkB;GACnE,EAAiC,CAAc;EACjD,QAAQ;GACN,EAAiC,EAAiC,CAAC;EACrE,UAAU;GACR,EAA2C,EAAK;EAClD;CACF,GAEM,KAAa,OAAO,MAAe;EACvC,IAAI;GACF,IAAM,IAAM,MAAM,MAAM,yBAAyB,mBAAmB,CAAE,EAAE,QAAQ;IAC9E,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,aAAa;IACb,WAAW;IACX,MAAM;GACR,CAAC,GAEK,IAAQ,MAAM,EAAI,KAAK,CAAC,CAAC,YAAY,IAAI;GAC/C,IAAI,CAAC,EAAI,MAAM,CAAC,GAAM,IAAI;GAE1B,IAAM,qBAAS,IAAI,KAAK,EAAA,CAAE,YAAY;GACtC,IAAI,GAAU;GACd,GAAU,MAAY;IACpB,IAAM,IAAS,EAAQ,MAAM,MAAM,EAAE,OAAO,CAAE;IAO9C,OANI,KAAU,CAAC,EAAO,UACpB,GAAgB,MAAU,KAAK,IAAI,GAAG,IAAQ,CAAC,CAAC,GAE9C,KAAU,CAAC,EAAO,UACpB,GAAgB,MAAU,KAAK,IAAI,GAAG,IAAQ,CAAC,CAAC,GAE3C,EAAQ,KAAK,MAAO,EAAE,OAAO,KAAM,CAAC,EAAE,SAAS;KAAE,GAAG;KAAG,QAAQ;KAAQ,GAAI,EAAE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAO;IAAG,IAAI,CAAE;GAC9H,CAAC;EACH,QAAQ;GACN;EACF;CACF,GAEM,KAAU,OAAO,MAAe;EACpC,IAAI;GACF,IAAM,IAAM,MAAM,MAAM,yBAAyB,mBAAmB,CAAE,EAAE,WAAW;IACjF,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,aAAa;IACb,MAAM;GACR,CAAC,GAEK,IAAQ,MAAM,EAAI,KAAK,CAAC,CAAC,YAAY,IAAI;GAG/C,IAFI,CAAC,EAAI,MAAM,CAAC,GAAM,MAElB,GAAU;GACd,GAAU,MAAY;IACpB,IAAM,IAAS,EAAQ,MAAM,MAAM,EAAE,OAAO,CAAE;IAO9C,OANI,KAAU,CAAC,EAAO,UACpB,GAAgB,MAAU,KAAK,IAAI,GAAG,IAAQ,CAAC,CAAC,GAE9C,KAAU,CAAC,EAAO,UACpB,GAAgB,MAAU,KAAK,IAAI,GAAG,IAAQ,CAAC,CAAC,GAE3C,EAAQ,QAAQ,MAAM,EAAE,OAAO,CAAE;GAC1C,CAAC;EACH,QAAQ;GACN;EACF;CACF,GAEM,UAAqB;EACzB,EAAa,EAAK;CACpB;CAWA,OACE,kBAAC,GAAD;EAA4B;EAAM,eAVV,MAAsB;GAC9C,IAAI,GAAU;IACZ,EAAa,EAAI;IACjB;GACF;GAEA,EAAa;EACf;EAII,UAAA,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAA0B,WAAU;GAAiC,SAAS;EAAe,CAAA,GAC7F,kBAAC,GAAD;GACE,WAAW,EACT,+GACA,CACF;GAJF,UAAA,CAME,kBAAC,UAAD;IAAQ,WAAU;IAAlB,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAuB,WAAU;MAAwB,UAAA;KAAoC,CAAA,GAC7F,kBAAC,QAAD;MAAM,WAAU;MACb,UAAA,IAAoB,IAAI,GAAG,EAAkB,WAAW,IAAoB,IAAI,GAAG,EAAkB,QAAQ;KAC1G,CAAA,CACH;IACL,CAAA,GAAA,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,GAAe,MAAM,CAAC,CAAC;MACtC,WAAW,EACT,uCACA,IAAa,8BAA8B,qDAC7C;MAEC,UAAA,IAAa,WAAW;KACnB,CAAA,GACR,kBAAC,GAAD;MACE,MAAK;MACL,cAAW;MACX,WAAU;MACV,SAAS;MAET,UAAA,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KACH,CAAA,CACpB;IACC,CAAA,CAAA;GAER,CAAA,GAAA,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KACE,YAAY;KACZ,YAAY;KACZ,UAAU;IACX,CAAA,GACA,KACC,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA;IACxC,CAAA,IACH,IACF,kBAAC,OAAD;KAAK,WAAU;KAAmD,UAAA;IAAiB,CAAA,IACjF,EAAa,WAAW,IAC1B,kBAAC,OAAD;KAAK,WAAU;KAAkD,UAAA;IAAsB,CAAA,IAEvF,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MAAwH,UAAA;KAElI,CAAA,GACJ,EAAa,WAAW,IACvB,kBAAC,OAAD;MAAK,WAAU;MAAiD,UAAA;KAA6B,CAAA,IAE7F,EAAa,KAAK,MAChB,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MACZ,UAAA,EAAM;KACJ,CAAA,GACL,kBAAC,OAAD;MAAK,WAAU;MACZ,UAAA,EAAM,MAAM,KAAK,MAChB,kBAAC,GAAD;OAEE,cAAc;OACd,YAAY;OACZ,WAAW;OACX,UAAU;MACX,GALM,EAAE,EAKR,CACF;KACE,CAAA,CACF,EAAA,GAfK,UAAU,EAAM,KAerB,CACN,CAEA,EAAA,CAAA,GAEJ,IAAa,OACZ,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MAAwH,UAAA;KAElI,CAAA,GACJ,GAAW,WAAW,IACrB,kBAAC,OAAD;MAAK,WAAU;MAAiD,UAAA;KAA+B,CAAA,IAE/F,GAAW,KAAK,MACd,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MACZ,UAAA,EAAM;KACJ,CAAA,GACL,kBAAC,OAAD;MAAK,WAAU;MACZ,UAAA,EAAM,MAAM,KAAK,MAChB,kBAAC,GAAD;OAEE,cAAc;OACd,YAAY;OACZ,WAAW;OACX,UAAU;MACX,GALM,EAAE,EAKR,CACF;KACE,CAAA,CACF,EAAA,GAfK,YAAY,EAAM,KAevB,CACN,CAEA,EAAA,CAAA,CAEJ;IAEJ,CAAA,CAAA;GACgB,CAAA,CAAA;EACD,CAAA,CAAA,EAAA,CAAA;CACJ,CAAA;AAE1B;AAEA,SAAS,EAAA,GAQN;gBAR0C,EAC3C,eACA,eACA,gBAH2CA;CAS3C,IAAI,MAAe,WAAW,OAAO;CAErC,IAAI,MAAe,eAAe;EAE9BC,IAAAA;EADF,2DACG,IAAA,kBAAA,OAAD;GAAK,WAAU;GACb,UAAA;EACG,CAAA,yBAFLA;CAIJ;CAEA,IAAM,IAAW,MAAe,UAKzB,IAAA,IAAW,uCAAuC,0CADrDC;CACGC,AAAAA,EAAAA,OAAAA,gBADH,IAAA,kBAAC,OAAD;EAAK,WAAU;EACZA,UAAAA;CACE,CAAA,GADFA,EAAAA,KAAAA;CAGA,IAAA,IAAA,IACG,mFACA,6DAHNC;CACGC,AAAAA,EAAAA,OAAAA,gBADH,IAAA,kBAAC,OAAD;EAAK,WAAU;EACZA,UAAAA;CAGE,CAAA,GAHFA,EAAAA,KAAAA;CAUA,IAAA,IAAA,IAAa,gBAAgB,gCANhCC;CAIW,AAAA,EAAA,OAAA,KAAA,EAAA,OADC,KAAA,EAAA,OAGTC,KANH,IAAA,kBAAC,UAAD;EACE,MAAK;EACL,WAAU;EACV,UAAU;EACV,SAAS;EAERA,UAAAA;CACK,CAAA,GAHG,EAAA,KAAA,GADC,EAAA,KAAA,GAGTA,EAAAA,KAAAA;CAfLC,IAAAA;CADF,OAEIN,EAAAA,OAAAA,KAAAA,EAAAA,QAGAE,KAAAA,EAAAA,QAKAE,KATF,IAAA,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACEJ;GAGAE;GAKAE;EAQG;KAhBHJ,EAAAA,KAAAA,GAGAE,EAAAA,MAAAA,GAKAE,EAAAA,MAAAA,2BATFE;AAmBJ;AAEA,SAAS,EAAA,GAUN;gBAVsB,EACvB,iBACA,eACA,cACA,gBAJuBC,GAWjB,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,IAAU,EAA8B,IAAI,GAC5C,IAAgB,EAAiC,IAAI,GACrD,IAAW,CAAC,EAAa,QACzB,GACcC;CADiB,AAAA,EAAA,OAAA,4BAArC,IAAe,EAAsB,CAAY,GAC7B,IAAA,EAA2B,CAAM,GADhB,EAAA,KAAA;CACrC,IAAM,IAAcA,GACGC;CACmBC,IAAAA,EAAAA,OAAAA,EAAAA,WAAAA;;GAAxC,IAAM,IAAO,IAAI,KAAK,EAAK,EAAa,SAAS,CAAC;GAClD,IAAI,CAAC,OAAO,SAAS,EAAK,QAAQ,CAAC,GAAA;IAAG,IAAO;IAAP,MAAA;;GACtC,IAAO,EAAK,eAAe;;EAFaA,EAAAA,KAAAA,EAAAA;;CAD1C,IAAM,IAAiBD,GAMbE,GAuBPC;CAvBH,AACO,EAAA,OAAA,4BADG,UAAM;EACd,IAAI,CAAC,GAAU;EAEf,IAAM,KAAqB,MAAwB;GACjD,IAAM,IAAS,EAAM;GACf,aAAkB,SACpB,EAAQ,SAAS,SAAS,CAAM,KAAK,EAAc,SAAS,SAAS,CAAM,KAC/E,EAAY,EAAK;EACnB,GAEM,KAAiB,MAAyB;GAC1CC,EAAM,QAAQ,aAClB,EAAY,EAAK,GACjB,EAAc,SAAS,MAAM;EAC/B;EAKA,OAHA,SAAS,iBAAiB,eAAe,CAAiB,GAC1D,SAAS,iBAAiB,WAAW,CAAa,SAErC;GAEX,AADA,SAAS,oBAAoB,eAAe,CAAiB,GAC7D,SAAS,oBAAoB,WAAW,CAAa;EACvD;CACF,GAAG,IAAA,CAAC,CAAQ,GAtBL,EAAA,KAAA,wBADP,EAAUF,GAuBPC,CAAU;CAMP,IAAA,IAAA,IAAW,iBAAiB,iBAFnBE;CAETC,AAAAA,EAAAA,OAAAA,gBAFS,IAAA,EACT,wDACAA,CACF,GADEA,EAAAA,KAAAA;CAMSC,IAAAA;CAIH,AAAA,EAAA,QAAA,KAAA,EAAA,QAHA,KAAA,EAAA,QAC2BC,EAAAA,MAAAA,EAAAA,QAG7B,KAAA,EAAA,QAHK,KAFA,UAAM;EAIb,AAHI,KACG,EAAW,EAAa,EAAE,GAE7B,MACF,EAAS,GACT,EAAwB,CAAM;CAElC,GAJM,EAAA,MAAA,GAHA,EAAA,MAAA,GAC2BA,EAAAA,MAAAA,EAAAA,IAG7B,EAAA,MAAA,GAHK,EAAA,MAAA;CASPC,IAAAA;CAA8DC,AAAAA,EAAAA,QAAAA,EAAAA,qBAA9D,IAAA,kBAAC,OAAD;EAAK,WAAU;EAAkC,UAAA,EAAa;CAAW,CAAA,GAAXA,EAAAA,MAAAA,EAAAA;CAC7DC,IAAAA;CAAaC,AAAAA,EAAAA,QAAAA,EAAAA,oBAAb,IAAA,EAAa,OACZ,kBAAC,OAAD;EAAK,WAAU;EAAqD,UAAA,EAAa;CAAU,CAAA,IACzF,MAFUA,EAAAA,MAAAA,EAAAA;CAFhBC,IAAAA;CACEJ,AAAAA,EAAAA,QAAAA,KAAAA,EAAAA,QACCE,KAFH,IAAA,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACEF,GACCE,CAGE;KAJHF,EAAAA,MAAAA,GACCE,EAAAA,MAAAA;CAIFG,IAAAA;4BAAc,IAAA,IAAA,kBAAC,OAAD;EAAK,WAAU;EAAsC,UAAA;CAAiB,CAAA,IAAI,MAAxF,EAAA,MAAA;CACAC,IAAAA;4BACC,IAAA,IAAA,kBAAC,OAAD;EAAK,WAAU;EAA0C,UAAA;CAAoB,CAAA,IAC3E,MAFH,EAAA,MAAA;CApBHC,IAAAA;CAaEH,AAAAA,EAAAA,QAAAA,KAAAA,EAAAA,QAMCC,KAAAA,EAAAA,QACAC,KAAAA,EAAAA,QAjBQR,KAHX,IAAA,kBAAC,UAAD;EACE,MAAK;EACL,WAAU;EACV,SAASA;EAHX,UAAA;GAaEM;GAMCC;GACAC;EAGK;KAVNF,EAAAA,MAAAA,GAMCC,EAAAA,MAAAA,GACAC,EAAAA,MAAAA,GAjBQR,EAAAA,MAAAA;CA6BEU,IAAAA,GAITC;sDAJS,UAAM;EACb,EAAYC,CAAe;CAC7B,GAEA,IAAA,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;CAXrCC,IAAAA;CAMiB,AAAA,EAAA,QAAA,iBANjB,IAAA,kBAAC,UAAD;EACE,KAAK;EACL,MAAK;EACL,WAAU;EACV,cAAW;EACX,iBAAc;EACd,iBAAe;EACf,SAASH;EAITC,UAAAA;CACM,CAAA,GANS,EAAA,MAAA;CAOhBG,IAAAA;CAwBgB,AAAA,EAAA,QAAA,KAAA,EAAA,QAxBhB,KAAA,EAAA,QAe8Bb,EAAAA,MAAAA,EAAAA,QAAvB,KAAA,EAAA,QAYA,KA3BP,IAAA,IACC,kBAAC,OAAD;EACE,KAAK;EACL,MAAK;EACL,WAAW,EACT,wIACA,iCACF;EANF,UAAA,CAQE,kBAAC,UAAD;GACE,MAAK;GACL,MAAK;GACL,WAAU;GACV,eAAe;IAEb,AADA,EAAY,EAAK,GACjB,EAAU,EAAa,EAAE;GAC3B;GAEA,UAAA;EACM,CAAA,GACR,kBAAC,UAAD;GACE,MAAK;GACL,MAAK;GACL,WAAU;GACV,UAAU,CAAC;GACX,eAAe;IAEb,AADA,EAAY,EAAK,GACjB,EAAW,EAAa,EAAE;GAC5B;GAEA,UAAA;EACM,CAAA,CACL;CACH,CAAA,IAAA,MATa,EAAA,MAAA,GAxBhB,EAAA,MAAA,GAe8BA,EAAAA,MAAAA,EAAAA,IAAvB,EAAA,MAAA,GAYA,EAAA,MAAA;CAzCVc,IAAAA;CACEF,AAAAA,EAAAA,QAAAA,KAAAA,EAAAA,QAaCC,KAdH,IAAA,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACED,GAaCC,CAkCE;KA/CHD,EAAAA,MAAAA,GAaCC,EAAAA,MAAAA;CA5CLE,IAAAA;CADF,OAOIP,EAAAA,QAAAA,KAAAA,EAAAA,QAwBAM,KAAAA,EAAAA,QA7BWjB,KADb,IAAA,kBAAC,OAAD;EACE,WAAWA;EADb,UAAA,CAMEW,GAwBAM,CAiDG;KAzEHN,EAAAA,MAAAA,GAwBAM,EAAAA,MAAAA,GA7BWjB,EAAAA,MAAAA,2BADbkB;AAiFJ;AA1CwB,SAAA,EAAC,GAAA;CAAS,OAAA,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index421.js","names":["res","json","normalized","p","next","topic","e"],"sources":["../src/components/Notifications/NotificationsSettings.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\"\nimport { Loader2 } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport type { NotificationDigestFrequency, NotificationSettings, NotificationTopicCatalogItem, NotificationTopicPreference } from \"./types\"\n\n\ntype SettingsResponse = {\n ok?: boolean\n error?: string\n settings?: NotificationSettings\n}\n\nconst digestValues: Array<{ value: NotificationDigestFrequency; label: string }> = [\n { value: \"off\", label: \"Off\" },\n { value: \"daily\", label: \"Daily\" },\n { value: \"weekly\", label: \"Weekly\" },\n]\n\nconst normalizeSettings = (settings: NotificationSettings | undefined): NotificationSettings => {\n const digestFrequency = settings?.digestFrequency\n const frequency: NotificationDigestFrequency =\n digestFrequency === \"off\" || digestFrequency === \"daily\" || digestFrequency === \"weekly\"\n ? digestFrequency\n : \"weekly\"\n\n const topicPreferences = Array.isArray(settings?.topicPreferences)\n ? settings!.topicPreferences\n .map((pref) => ({\n topic: typeof pref.topic === \"string\" ? pref.topic.trim() : \"\",\n inApp: pref.inApp === true,\n emailDigest: pref.emailDigest === true,\n push: pref.push === true,\n }))\n .filter((pref) => pref.topic.length > 0)\n : []\n\n return {\n digestFrequency: frequency,\n topicPreferences,\n ...(typeof settings?.lastDigestSentAt === \"string\" ? { lastDigestSentAt: settings.lastDigestSentAt } : {}),\n }\n}\n\nconst normalizeTopics = (topics: NotificationTopicCatalogItem[] | undefined): NotificationTopicCatalogItem[] => {\n if (!Array.isArray(topics)) return []\n\n const seen = new Set<string>()\n return topics\n .map((topic) => ({\n topic: typeof topic.topic === \"string\" ? topic.topic.trim() : \"\",\n title: typeof topic.title === \"string\" ? topic.title.trim() : \"\",\n description: typeof topic.description === \"string\" ? topic.description.trim() : undefined,\n }))\n .filter((topic) => topic.topic.length > 0)\n .filter((topic) => {\n if (seen.has(topic.topic)) return false\n seen.add(topic.topic)\n return true\n })\n}\n\nconst applyTopicsToSettings = (\n settings: NotificationSettings,\n topics: NotificationTopicCatalogItem[],\n): NotificationSettings => {\n const defaults: NotificationTopicPreference = {\n topic: \"\",\n inApp: true,\n emailDigest: true,\n push: false,\n }\n\n const byTopic = new Map(settings.topicPreferences.map((pref) => [pref.topic, pref]))\n return {\n ...settings,\n topicPreferences: topics.map((topic) => {\n const existing = byTopic.get(topic.topic)\n return existing\n ? {\n topic: topic.topic,\n inApp: existing.inApp === true,\n emailDigest: existing.emailDigest === true,\n push: existing.push === true,\n }\n : {\n ...defaults,\n topic: topic.topic,\n }\n }),\n }\n}\n\nexport function NotificationsSettings({\n className,\n topics,\n}: {\n className?: string\n topics?: NotificationTopicCatalogItem[]\n}) {\n const [loading, setLoading] = useState(true)\n const [saving, setSaving] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [settings, setSettings] = useState<NotificationSettings>(() =>\n normalizeSettings({ digestFrequency: \"weekly\", topicPreferences: [] }),\n )\n\n const normalizedTopics = useMemo(() => normalizeTopics(topics), [topics])\n\n const loadSettings = async () => {\n setLoading(true)\n setError(null)\n try {\n const res = await fetch(\"/api/rb/notifications/settings\", {\n method: \"GET\",\n credentials: \"include\",\n })\n const json = (await res.json().catch(() => null)) as SettingsResponse | null\n if (!res.ok || !json?.ok) {\n setError(json?.error || \"Unable to load notification settings.\")\n return\n }\n const normalized = normalizeSettings(json.settings)\n setSettings(normalizedTopics.length > 0 ? applyTopicsToSettings(normalized, normalizedTopics) : normalized)\n } catch {\n setError(\"Unable to load notification settings.\")\n } finally {\n setLoading(false)\n }\n }\n\n const saveSettings = async (next: NotificationSettings) => {\n setSaving(true)\n setError(null)\n try {\n const res = await fetch(\"/api/rb/notifications/settings\", {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify({\n digestFrequency: next.digestFrequency,\n topicPreferences: next.topicPreferences,\n }),\n })\n const json = (await res.json().catch(() => null)) as SettingsResponse | null\n if (!res.ok || !json?.ok) {\n setError(json?.error || \"Unable to save notification settings.\")\n return\n }\n const normalized = normalizeSettings(json.settings)\n setSettings(normalizedTopics.length > 0 ? applyTopicsToSettings(normalized, normalizedTopics) : normalized)\n } catch {\n setError(\"Unable to save notification settings.\")\n } finally {\n setSaving(false)\n }\n }\n\n useEffect(() => {\n void loadSettings()\n }, [])\n\n useEffect(() => {\n if (normalizedTopics.length === 0) return\n setSettings((current) => applyTopicsToSettings(current, normalizedTopics))\n }, [normalizedTopics])\n\n const updatePreference = (topic: string, update: (p: NotificationTopicPreference) => NotificationTopicPreference) => {\n const hasExisting = settings.topicPreferences.some((p) => p.topic === topic)\n const nextPrefs = hasExisting\n ? settings.topicPreferences.map((p) => (p.topic === topic ? update(p) : p))\n : [...settings.topicPreferences, update({ topic, inApp: true, emailDigest: true, push: false })]\n const next = normalizeSettings({ ...settings, topicPreferences: nextPrefs })\n setSettings(next)\n void saveSettings(next)\n }\n\n if (loading) {\n return (\n <div className={cn(\"flex items-center justify-center py-10 text-muted-foreground\", className)}>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n </div>\n )\n }\n\n return (\n <div className={cn(\"space-y-4\", className)}>\n <div className=\"rounded-md border bg-background p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <div className=\"text-sm font-semibold\">Email digest</div>\n <div className=\"mt-1 text-xs text-muted-foreground\">Choose how often we email you a summary.</div>\n </div>\n <select\n value={settings.digestFrequency}\n onChange={(e) => {\n const value = e.target.value as NotificationDigestFrequency\n const next = normalizeSettings({ ...settings, digestFrequency: value })\n setSettings(next)\n void saveSettings(next)\n }}\n className=\"h-9 rounded-md border bg-background px-2 text-sm\"\n >\n {digestValues.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"rounded-md border bg-background p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <div className=\"text-sm font-semibold\">Topics</div>\n <div className=\"mt-1 text-xs text-muted-foreground\">Choose where you receive each topic.</div>\n </div>\n </div>\n\n {normalizedTopics.length === 0 ? (\n <div className=\"mt-4 text-sm text-muted-foreground\">No topics configured.</div>\n ) : (\n <div className=\"mt-4 overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-2 pr-4 font-medium\">Topic</th>\n <th className=\"py-2 pr-4 font-medium\">In-app</th>\n <th className=\"py-2 pr-4 font-medium\">Email digest</th>\n <th className=\"py-2 pr-4 font-medium\">Push</th>\n </tr>\n </thead>\n <tbody>\n {settings.topicPreferences.map((pref) => {\n const meta = normalizedTopics.find((topic) => topic.topic === pref.topic)\n return (\n <tr key={pref.topic} className=\"border-t align-top\">\n <td className=\"py-3 pr-4\">\n <div className=\"flex flex-col gap-1\">\n <span className=\"font-medium\">{meta?.title || pref.topic}</span>\n {meta?.description ? (\n <span className=\"text-xs text-muted-foreground\">{meta.description}</span>\n ) : null}\n <span className=\"text-[11px] text-muted-foreground\">{pref.topic}</span>\n </div>\n </td>\n <td className=\"py-3 pr-4\">\n <input\n type=\"checkbox\"\n checked={pref.inApp}\n onChange={(e) =>\n updatePreference(pref.topic, (p) => ({ ...p, inApp: e.target.checked }))\n }\n />\n </td>\n <td className=\"py-3 pr-4\">\n <input\n type=\"checkbox\"\n checked={pref.emailDigest}\n onChange={(e) =>\n updatePreference(pref.topic, (p) => ({ ...p, emailDigest: e.target.checked }))\n }\n />\n </td>\n <td className=\"py-3 pr-4\">\n <input\n type=\"checkbox\"\n checked={pref.push}\n disabled\n className=\"opacity-50\"\n aria-label=\"Push notifications (coming soon)\"\n />\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n {error ? (\n <div className=\"mt-3 text-sm text-destructive\">{error}</div>\n ) : saving ? (\n <div className=\"mt-3 text-xs text-muted-foreground\">Saving…</div>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index421.js","names":[],"sources":["../src/components/Notifications/NotificationsSettings.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\"\nimport { Loader2 } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport type { NotificationDigestFrequency, NotificationSettings, NotificationTopicCatalogItem, NotificationTopicPreference } from \"./types\"\n\n\ntype SettingsResponse = {\n ok?: boolean\n error?: string\n settings?: NotificationSettings\n}\n\nconst digestValues: Array<{ value: NotificationDigestFrequency; label: string }> = [\n { value: \"off\", label: \"Off\" },\n { value: \"daily\", label: \"Daily\" },\n { value: \"weekly\", label: \"Weekly\" },\n]\n\nconst normalizeSettings = (settings: NotificationSettings | undefined): NotificationSettings => {\n const digestFrequency = settings?.digestFrequency\n const frequency: NotificationDigestFrequency =\n digestFrequency === \"off\" || digestFrequency === \"daily\" || digestFrequency === \"weekly\"\n ? digestFrequency\n : \"weekly\"\n\n const topicPreferences = Array.isArray(settings?.topicPreferences)\n ? settings!.topicPreferences\n .map((pref) => ({\n topic: typeof pref.topic === \"string\" ? pref.topic.trim() : \"\",\n inApp: pref.inApp === true,\n emailDigest: pref.emailDigest === true,\n push: pref.push === true,\n }))\n .filter((pref) => pref.topic.length > 0)\n : []\n\n return {\n digestFrequency: frequency,\n topicPreferences,\n ...(typeof settings?.lastDigestSentAt === \"string\" ? { lastDigestSentAt: settings.lastDigestSentAt } : {}),\n }\n}\n\nconst normalizeTopics = (topics: NotificationTopicCatalogItem[] | undefined): NotificationTopicCatalogItem[] => {\n if (!Array.isArray(topics)) return []\n\n const seen = new Set<string>()\n return topics\n .map((topic) => ({\n topic: typeof topic.topic === \"string\" ? topic.topic.trim() : \"\",\n title: typeof topic.title === \"string\" ? topic.title.trim() : \"\",\n description: typeof topic.description === \"string\" ? topic.description.trim() : undefined,\n }))\n .filter((topic) => topic.topic.length > 0)\n .filter((topic) => {\n if (seen.has(topic.topic)) return false\n seen.add(topic.topic)\n return true\n })\n}\n\nconst applyTopicsToSettings = (\n settings: NotificationSettings,\n topics: NotificationTopicCatalogItem[],\n): NotificationSettings => {\n const defaults: NotificationTopicPreference = {\n topic: \"\",\n inApp: true,\n emailDigest: true,\n push: false,\n }\n\n const byTopic = new Map(settings.topicPreferences.map((pref) => [pref.topic, pref]))\n return {\n ...settings,\n topicPreferences: topics.map((topic) => {\n const existing = byTopic.get(topic.topic)\n return existing\n ? {\n topic: topic.topic,\n inApp: existing.inApp === true,\n emailDigest: existing.emailDigest === true,\n push: existing.push === true,\n }\n : {\n ...defaults,\n topic: topic.topic,\n }\n }),\n }\n}\n\nexport function NotificationsSettings({\n className,\n topics,\n}: {\n className?: string\n topics?: NotificationTopicCatalogItem[]\n}) {\n const [loading, setLoading] = useState(true)\n const [saving, setSaving] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [settings, setSettings] = useState<NotificationSettings>(() =>\n normalizeSettings({ digestFrequency: \"weekly\", topicPreferences: [] }),\n )\n\n const normalizedTopics = useMemo(() => normalizeTopics(topics), [topics])\n\n const loadSettings = async () => {\n setLoading(true)\n setError(null)\n try {\n const res = await fetch(\"/api/rb/notifications/settings\", {\n method: \"GET\",\n credentials: \"include\",\n })\n const json = (await res.json().catch(() => null)) as SettingsResponse | null\n if (!res.ok || !json?.ok) {\n setError(json?.error || \"Unable to load notification settings.\")\n return\n }\n const normalized = normalizeSettings(json.settings)\n setSettings(normalizedTopics.length > 0 ? applyTopicsToSettings(normalized, normalizedTopics) : normalized)\n } catch {\n setError(\"Unable to load notification settings.\")\n } finally {\n setLoading(false)\n }\n }\n\n const saveSettings = async (next: NotificationSettings) => {\n setSaving(true)\n setError(null)\n try {\n const res = await fetch(\"/api/rb/notifications/settings\", {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify({\n digestFrequency: next.digestFrequency,\n topicPreferences: next.topicPreferences,\n }),\n })\n const json = (await res.json().catch(() => null)) as SettingsResponse | null\n if (!res.ok || !json?.ok) {\n setError(json?.error || \"Unable to save notification settings.\")\n return\n }\n const normalized = normalizeSettings(json.settings)\n setSettings(normalizedTopics.length > 0 ? applyTopicsToSettings(normalized, normalizedTopics) : normalized)\n } catch {\n setError(\"Unable to save notification settings.\")\n } finally {\n setSaving(false)\n }\n }\n\n useEffect(() => {\n void loadSettings()\n }, [])\n\n useEffect(() => {\n if (normalizedTopics.length === 0) return\n setSettings((current) => applyTopicsToSettings(current, normalizedTopics))\n }, [normalizedTopics])\n\n const updatePreference = (topic: string, update: (p: NotificationTopicPreference) => NotificationTopicPreference) => {\n const hasExisting = settings.topicPreferences.some((p) => p.topic === topic)\n const nextPrefs = hasExisting\n ? settings.topicPreferences.map((p) => (p.topic === topic ? update(p) : p))\n : [...settings.topicPreferences, update({ topic, inApp: true, emailDigest: true, push: false })]\n const next = normalizeSettings({ ...settings, topicPreferences: nextPrefs })\n setSettings(next)\n void saveSettings(next)\n }\n\n if (loading) {\n return (\n <div className={cn(\"flex items-center justify-center py-10 text-muted-foreground\", className)}>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n </div>\n )\n }\n\n return (\n <div className={cn(\"space-y-4\", className)}>\n <div className=\"rounded-md border bg-background p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <div className=\"text-sm font-semibold\">Email digest</div>\n <div className=\"mt-1 text-xs text-muted-foreground\">Choose how often we email you a summary.</div>\n </div>\n <select\n value={settings.digestFrequency}\n onChange={(e) => {\n const value = e.target.value as NotificationDigestFrequency\n const next = normalizeSettings({ ...settings, digestFrequency: value })\n setSettings(next)\n void saveSettings(next)\n }}\n className=\"h-9 rounded-md border bg-background px-2 text-sm\"\n >\n {digestValues.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"rounded-md border bg-background p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <div className=\"text-sm font-semibold\">Topics</div>\n <div className=\"mt-1 text-xs text-muted-foreground\">Choose where you receive each topic.</div>\n </div>\n </div>\n\n {normalizedTopics.length === 0 ? (\n <div className=\"mt-4 text-sm text-muted-foreground\">No topics configured.</div>\n ) : (\n <div className=\"mt-4 overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-2 pr-4 font-medium\">Topic</th>\n <th className=\"py-2 pr-4 font-medium\">In-app</th>\n <th className=\"py-2 pr-4 font-medium\">Email digest</th>\n <th className=\"py-2 pr-4 font-medium\">Push</th>\n </tr>\n </thead>\n <tbody>\n {settings.topicPreferences.map((pref) => {\n const meta = normalizedTopics.find((topic) => topic.topic === pref.topic)\n return (\n <tr key={pref.topic} className=\"border-t align-top\">\n <td className=\"py-3 pr-4\">\n <div className=\"flex flex-col gap-1\">\n <span className=\"font-medium\">{meta?.title || pref.topic}</span>\n {meta?.description ? (\n <span className=\"text-xs text-muted-foreground\">{meta.description}</span>\n ) : null}\n <span className=\"text-[11px] text-muted-foreground\">{pref.topic}</span>\n </div>\n </td>\n <td className=\"py-3 pr-4\">\n <input\n type=\"checkbox\"\n checked={pref.inApp}\n onChange={(e) =>\n updatePreference(pref.topic, (p) => ({ ...p, inApp: e.target.checked }))\n }\n />\n </td>\n <td className=\"py-3 pr-4\">\n <input\n type=\"checkbox\"\n checked={pref.emailDigest}\n onChange={(e) =>\n updatePreference(pref.topic, (p) => ({ ...p, emailDigest: e.target.checked }))\n }\n />\n </td>\n <td className=\"py-3 pr-4\">\n <input\n type=\"checkbox\"\n checked={pref.push}\n disabled\n className=\"opacity-50\"\n aria-label=\"Push notifications (coming soon)\"\n />\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n {error ? (\n <div className=\"mt-3 text-sm text-destructive\">{error}</div>\n ) : saving ? (\n <div className=\"mt-3 text-xs text-muted-foreground\">Saving…</div>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":";;;;;AAcA,IAAM,IAA6E;CACjF;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAS,OAAO;CAAQ;CACjC;EAAE,OAAO;EAAU,OAAO;CAAS;AACrC,GAEM,KAAqB,MAAqE;CAC9F,IAAM,IAAkB,GAAU;CAiBlC,OAAO;EACL,iBAhBA,MAAoB,SAAS,MAAoB,WAAW,MAAoB,WAC5E,IACA;EAeJ,kBAbuB,MAAM,QAAQ,GAAU,gBAAgB,IAC7D,EAAU,iBACT,KAAK,OAAU;GACd,OAAO,OAAO,EAAK,SAAU,WAAW,EAAK,MAAM,KAAK,IAAI;GAC5D,OAAO,EAAK,UAAU;GACtB,aAAa,EAAK,gBAAgB;GAClC,MAAM,EAAK,SAAS;EACtB,EAAE,CAAC,CACF,QAAQ,MAAS,EAAK,MAAM,SAAS,CAAC,IACvC,CAAC;EAKH,GAAI,OAAO,GAAU,oBAAqB,WAAW,EAAE,kBAAkB,EAAS,iBAAiB,IAAI,CAAC;CAC1G;AACF,GAEM,KAAmB,MAAuF;CAC9G,IAAI,CAAC,MAAM,QAAQ,CAAM,GAAG,OAAO,CAAC;CAEpC,IAAM,oBAAO,IAAI,IAAY;CAC7B,OAAO,EACJ,KAAK,OAAW;EACf,OAAO,OAAO,EAAM,SAAU,WAAW,EAAM,MAAM,KAAK,IAAI;EAC9D,OAAO,OAAO,EAAM,SAAU,WAAW,EAAM,MAAM,KAAK,IAAI;EAC9D,aAAa,OAAO,EAAM,eAAgB,WAAW,EAAM,YAAY,KAAK,IAAI,KAAA;CAClF,EAAE,CAAC,CACF,QAAQ,MAAU,EAAM,MAAM,SAAS,CAAC,CAAC,CACzC,QAAQ,MACP,CAAI,EAAK,IAAI,EAAM,KAAK,MACxB,EAAK,IAAI,EAAM,KAAK,GACb,GACR;AACL,GAEM,KACJ,GACA,MACyB;CACzB,IAAM,IAAwC;EAC5C,OAAO;EACP,OAAO;EACP,aAAa;EACb,MAAM;CACR,GAEM,IAAU,IAAI,IAAI,EAAS,iBAAiB,KAAK,MAAS,CAAC,EAAK,OAAO,CAAI,CAAC,CAAC;CACnF,OAAO;EACL,GAAG;EACH,kBAAkB,EAAO,KAAK,MAAU;GACtC,IAAM,IAAW,EAAQ,IAAI,EAAM,KAAK;GACxC,OAAO,IACH;IACA,OAAO,EAAM;IACb,OAAO,EAAS,UAAU;IAC1B,aAAa,EAAS,gBAAgB;IACtC,MAAM,EAAS,SAAS;GAC1B,IACE;IACA,GAAG;IACH,OAAO,EAAM;GACf;EACJ,CAAC;CACH;AACF;AAEA,SAAgB,EAAsB,EACpC,cACA,aAIC;CACD,IAAM,CAAC,GAAS,KAAc,EAAS,EAAI,GACrC,CAAC,GAAQ,KAAa,EAAS,EAAK,GACpC,CAAC,GAAO,KAAY,EAAwB,IAAI,GAChD,CAAC,GAAU,KAAe,QAC9B,EAAkB;EAAE,iBAAiB;EAAU,kBAAkB,CAAC;CAAE,CAAC,CACvE,GAEM,IAAmB,QAAc,EAAgB,CAAM,GAAG,CAAC,CAAM,CAAC,GAElE,IAAe,YAAY;EAE/B,AADA,EAAW,EAAI,GACf,EAAS,IAAI;EACb,IAAI;GACF,IAAM,IAAM,MAAM,MAAM,kCAAkC;IACxD,QAAQ;IACR,aAAa;GACf,CAAC,GACK,IAAQ,MAAM,EAAI,KAAK,CAAC,CAAC,YAAY,IAAI;GAC/C,IAAI,CAAC,EAAI,MAAM,CAAC,GAAM,IAAI;IACxB,EAAS,GAAM,SAAS,uCAAuC;IAC/D;GACF;GACA,IAAM,IAAa,EAAkB,EAAK,QAAQ;GAClD,EAAY,EAAiB,SAAS,IAAI,EAAsB,GAAY,CAAgB,IAAI,CAAU;EAC5G,QAAQ;GACN,EAAS,uCAAuC;EAClD,UAAU;GACR,EAAW,EAAK;EAClB;CACF,GAEM,IAAe,OAAO,MAA+B;EAEzD,AADA,EAAU,EAAI,GACd,EAAS,IAAI;EACb,IAAI;GACF,IAAM,IAAM,MAAM,MAAM,kCAAkC;IACxD,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,aAAa;IACb,MAAM,KAAK,UAAU;KACnB,iBAAiB,EAAK;KACtB,kBAAkB,EAAK;IACzB,CAAC;GACH,CAAC,GACK,IAAQ,MAAM,EAAI,KAAK,CAAC,CAAC,YAAY,IAAI;GAC/C,IAAI,CAAC,EAAI,MAAM,CAAC,GAAM,IAAI;IACxB,EAAS,GAAM,SAAS,uCAAuC;IAC/D;GACF;GACA,IAAM,IAAa,EAAkB,EAAK,QAAQ;GAClD,EAAY,EAAiB,SAAS,IAAI,EAAsB,GAAY,CAAgB,IAAI,CAAU;EAC5G,QAAQ;GACN,EAAS,uCAAuC;EAClD,UAAU;GACR,EAAU,EAAK;EACjB;CACF;CAMA,AAJA,QAAgB;EACd,EAAkB;CACpB,GAAG,CAAC,CAAC,GAEL,QAAgB;EACV,EAAiB,WAAW,KAChC,GAAa,MAAY,EAAsB,GAAS,CAAgB,CAAC;CAC3E,GAAG,CAAC,CAAgB,CAAC;CAErB,IAAM,KAAoB,GAAe,MAA4E;EAEnH,IAAM,IADc,EAAS,iBAAiB,MAAM,MAAM,EAAE,UAAU,CACpD,IACd,EAAS,iBAAiB,KAAK,MAAO,EAAE,UAAU,IAAQ,EAAO,CAAC,IAAI,CAAE,IACxE,CAAC,GAAG,EAAS,kBAAkB,EAAO;GAAE;GAAO,OAAO;GAAM,aAAa;GAAM,MAAM;EAAM,CAAC,CAAC,GAC3F,IAAO,EAAkB;GAAE,GAAG;GAAU,kBAAkB;EAAU,CAAC;EAE3E,AADA,EAAY,CAAI,GAChB,EAAkB,CAAI;CACxB;CAUA,OARI,IAEA,kBAAC,OAAD;EAAK,WAAW,EAAG,gEAAgE,CAAS;EAC1F,UAAA,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA;CACxC,CAAA,IAKP,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,CAAS;EAAzC,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GACb,UAAA,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAwB,UAAA;IAAiB,CAAA,GACxD,kBAAC,OAAD;KAAK,WAAU;KAAqC,UAAA;IAA6C,CAAA,CAC9F,EAAA,CAAA,GACL,kBAAC,UAAD;KACE,OAAO,EAAS;KAChB,WAAW,MAAM;MACf,IAAM,IAAQ,EAAE,OAAO,OACjB,IAAO,EAAkB;OAAE,GAAG;OAAU,iBAAiB;MAAM,CAAC;MAEtE,AADA,EAAY,CAAI,GAChB,EAAkB,CAAI;KACxB;KACA,WAAU;KAET,UAAA,EAAa,KAAK,MACjB,kBAAC,UAAD;MAAwB,OAAO,EAAI;MAChC,UAAA,EAAI;KACC,GAFK,EAAI,KAET,CACT;IACK,CAAA,CACL;;EACF,CAAA,GAEL,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MAAwB,UAAA;KAAW,CAAA,GAClD,kBAAC,OAAD;MAAK,WAAU;MAAqC,UAAA;KAAyC,CAAA,CAC1F,EAAA,CAAA;IACF,CAAA;IAEJ,EAAiB,WAAW,IAC3B,kBAAC,OAAD;KAAK,WAAU;KAAqC,UAAA;IAA0B,CAAA,IAE9E,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,SAAD;MAAO,WAAU;MAAjB,UAAA,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;OAAI,WAAU;OAAd,UAAA;QACE,kBAAC,MAAD;SAAI,WAAU;SAAwB,UAAA;QAAS,CAAA;QAC/C,kBAAC,MAAD;SAAI,WAAU;SAAwB,UAAA;QAAU,CAAA;QAChD,kBAAC,MAAD;SAAI,WAAU;SAAwB,UAAA;QAAgB,CAAA;QACtD,kBAAC,MAAD;SAAI,WAAU;SAAwB,UAAA;QAAQ,CAAA;OAC5C;MACC,CAAA,EAAA,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAS,iBAAiB,KAAK,MAAS;OACvC,IAAM,IAAO,EAAiB,MAAM,MAAU,EAAM,UAAU,EAAK,KAAK;OACxE,OACE,kBAAC,MAAD;QAAqB,WAAU;QAA/B,UAAA;SACE,kBAAC,MAAD;UAAI,WAAU;UACZ,UAAA,kBAAC,OAAD;WAAK,WAAU;WAAf,UAAA;YACE,kBAAC,QAAD;aAAM,WAAU;aAAe,UAAA,GAAM,SAAS,EAAK;YAAY,CAAA;YAC9D,GAAM,cACL,kBAAC,QAAD;aAAM,WAAU;aAAiC,UAAA,EAAK;YAAkB,CAAA,IACtE;YACJ,kBAAC,QAAD;aAAM,WAAU;aAAqC,UAAA,EAAK;YAAY,CAAA;WACnE;;SACH,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;UACZ,UAAA,kBAAC,SAAD;WACE,MAAK;WACL,SAAS,EAAK;WACd,WAAW,MACT,EAAiB,EAAK,QAAQ,OAAO;YAAE,GAAG;YAAG,OAAO,EAAE,OAAO;WAAQ,EAAE;UAE1E,CAAA;SACC,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;UACZ,UAAA,kBAAC,SAAD;WACE,MAAK;WACL,SAAS,EAAK;WACd,WAAW,MACT,EAAiB,EAAK,QAAQ,OAAO;YAAE,GAAG;YAAG,aAAa,EAAE,OAAO;WAAQ,EAAE;UAEhF,CAAA;SACC,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;UACZ,UAAA,kBAAC,SAAD;WACE,MAAK;WACL,SAAS,EAAK;WACd,UAAA;WACA,WAAU;WACV,cAAW;UACZ,CAAA;SACC,CAAA;QACF;OArCK,GAAA,EAAK,KAqCV;MAER,CAAC,EACI,CAAA,CACF;;IACJ,CAAA;IAGN,IACC,kBAAC,OAAD;KAAK,WAAU;KAAiC,UAAA;IAAW,CAAA,IACzD,IACF,kBAAC,OAAD;KAAK,WAAU;KAAqC,UAAA;IAAY,CAAA,IAC9D;GACD;EACF,CAAA,CAAA;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"index6.js","names":["cancelled"],"sources":["../src/helpers/useMapsLibrary.ts"],"sourcesContent":["/// <reference types=\"google.maps\" />\n\nimport { useEffect, useState } from \"react\"\nimport { setOptions, importLibrary } from \"@googlemaps/js-api-loader\"\n\n\ntype GoogleMapsLibraryName =\n | \"core\"\n | \"maps\"\n | \"maps3d\"\n | \"routes\"\n | \"geometry\"\n | \"elevation\"\n | \"streetView\"\n | \"journeySharing\"\n | \"drawing\"\n | \"visualization\"\n | \"marker\"\n | \"places\"\n | \"geocoding\"\n | (string & {})\n\ntype GoogleMapsLibraryModule<T extends GoogleMapsLibraryName> =\n T extends \"core\" ? google.maps.CoreLibrary\n : T extends \"maps\" ? google.maps.MapsLibrary\n : T extends \"maps3d\" ? google.maps.Maps3DLibrary\n : T extends \"routes\" ? google.maps.RoutesLibrary\n : T extends \"geometry\" ? google.maps.GeometryLibrary\n : T extends \"elevation\" ? google.maps.ElevationLibrary\n : T extends \"streetView\" ? google.maps.StreetViewLibrary\n : T extends \"journeySharing\" ? google.maps.JourneySharingLibrary\n : T extends \"drawing\" ? google.maps.DrawingLibrary\n : T extends \"visualization\" ? google.maps.VisualizationLibrary\n : T extends \"marker\" ? google.maps.MarkerLibrary\n : T extends \"places\" ? google.maps.PlacesLibrary\n : T extends \"geocoding\" ? google.maps.GeocodingLibrary\n : unknown\n\n\nlet initPromise: Promise<void> | null = null\nlet configuredKey: string | null = null\n\nconst libraryPromises = new Map<string, Promise<unknown>>()\n\nconst ensureGoogle = (apiKey?: string) => {\n if (!apiKey) {\n const error = new Error(\"useMapsLibrary: missing Google Maps API key\")\n console.error(error.message)\n return Promise.reject(error)\n }\n\n if (!initPromise) {\n configuredKey = apiKey\n setOptions({\n key: apiKey,\n v: \"weekly\",\n })\n initPromise = importLibrary(\"core\")\n .then(() => {})\n .catch(error => {\n initPromise = null\n throw error\n })\n } else if (configuredKey !== apiKey) {\n console.warn(\n \"useMapsLibrary: ignoring attempt to re-initialize Google Maps with a different API key than the one already in use.\"\n )\n }\n\n return initPromise\n}\n\nconst loadLibrary = <T extends GoogleMapsLibraryName>(\n library: T,\n apiKey?: string\n) => {\n const cacheKey = String(library)\n const cached = libraryPromises.get(cacheKey)\n if (cached) {\n return cached as Promise<GoogleMapsLibraryModule<T>>\n }\n\n const promise = ensureGoogle(apiKey)\n .then(() => {\n if (typeof window === \"undefined\" || !window.google?.maps?.importLibrary) {\n throw new Error(\"useMapsLibrary: google.maps.importLibrary is not available.\")\n }\n return window.google.maps.importLibrary(cacheKey)\n })\n .then(module => module as GoogleMapsLibraryModule<T>)\n .catch(error => {\n libraryPromises.delete(cacheKey)\n throw error\n })\n\n libraryPromises.set(cacheKey, promise as Promise<unknown>)\n return promise\n}\n\nexport const useMapsLibrary = <T extends GoogleMapsLibraryName>(\n library: T,\n apiKey?: string\n) => {\n const [module, setModule] =\n useState<GoogleMapsLibraryModule<T> | null>(null)\n\n useEffect(() => {\n if (typeof window === \"undefined\") {\n return\n }\n\n let cancelled = false\n\n loadLibrary(library, apiKey)\n .then(loadedModule => {\n if (!cancelled) {\n setModule(loadedModule)\n }\n })\n .catch(error => {\n if (!cancelled) {\n console.error(`useMapsLibrary: failed to load ${String(library)} library`, error)\n setModule(null)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [library, apiKey])\n\n return module\n}\n\nexport type { GoogleMapsLibraryName, GoogleMapsLibraryModule }\n"],"mappings":";;;;AAuCA,IAAI,IAAoC,MACpC,IAA+B,MAE7B,oBAAkB,IAAI,IAA8B,GAEpD,KAAgB,MAAoB;CACxC,IAAI,CAAC,GAAQ;EACX,IAAM,IAAQ,gBAAI,MAAM,6CAA6C;EAErE,OADA,QAAQ,MAAM,EAAM,OAAO,GACpB,QAAQ,OAAO,CAAK;CAC7B;CAoBA,OAlBK,IAYM,MAAkB,KAC3B,QAAQ,KACN,qHACF,KAdA,IAAgB,GAChB,EAAW;EACT,KAAK;EACL,GAAG;CACL,CAAC,GACD,IAAc,EAAc,MAAM,CAAC,CAChC,WAAW,CAAC,CAAC,CAAC,CACd,OAAM,MAAS;EAEd,MADA,IAAc,MACR;CACR,CAAC,IAOE;AACT,GAEM,KACJ,GACA,MACG;CACH,IAAM,IAAW,OAAO,CAAO,GACzB,IAAS,EAAgB,IAAI,CAAQ;CAC3C,IAAI,GACF,OAAO;CAGT,IAAM,IAAU,EAAa,CAAM,CAAC,CACjC,WAAW;EACV,IAAI,OAAO,SAAW,OAAe,CAAC,OAAO,QAAQ,MAAM,eACzD,MAAU,MAAM,6DAA6D;EAE/E,OAAO,OAAO,OAAO,KAAK,cAAc,CAAQ;CAClD,CAAC,CAAC,CACD,MAAK,MAAU,CAAoC,CAAC,CACpD,OAAM,MAAS;EAEd,MADA,EAAgB,OAAO,CAAQ,GACzB;CACR,CAAC;CAGH,OADA,EAAgB,IAAI,GAAU,CAA2B,GAClD;AACT,GAEa,KAAiB,GAAA,MAAA;CAAA,IAAA,IAAA,EAAA,CAAA,GAI5B,CAAA,GAAA,KACE,EAA4C,IAAI,GAAC,GAAA;CAyB9B,OAzB8B,EAAA,OAAA,KAAA,EAAA,OAAA,KAEzC,UAAA;EACR,IAAI,OAAO,SAAW,KAAW;EAIjC,IAAA,IAAgB;EAaZ,OAXJ,EAAY,GAAS,CAAM,CAAC,CAAA,MACpB,MAAA;GACJ,AAAK,KACH,EAAU,CAAY;EACvB,CACF,CAAC,CAAA,OACK,MAAA;GACL,AAAK,MACH,QAAO,MAAO,kCAAkC,OAAO,CAAO,EAAC,WAAY,CAAK,GAChF,EAAU,IAAI;EACf,CACF,SAEI;GACL,IAAYA;EAAH;CACV,GACA,IAAA,CAAC,GAAS,CAAM,GAAC,EAAA,KAAA,GAAA,EAAA,KAAA,GAAA,EAAA,KAAA,GAAA,EAAA,KAAA,MAAA,IAAA,EAAA,IAAA,IAAA,EAAA,KAvBpB,EAAU,GAuBP,CAAiB,GAEb;AAAM"}
1
+ {"version":3,"file":"index6.js","names":["() => {\n if (typeof window === \"undefined\") {\n return\n }\n\n let cancelled = false\n\n loadLibrary(library, apiKey)\n .then(loadedModule => {\n if (!cancelled) {\n setModule(loadedModule)\n }\n })\n .catch(error => {\n if (!cancelled) {\n console.error(`useMapsLibrary: failed to load ${String(library)} library`, error)\n setModule(null)\n }\n })\n\n return () => {\n cancelled = true\n }\n }","[library, apiKey]"],"sources":["../src/helpers/useMapsLibrary.ts"],"sourcesContent":["/// <reference types=\"google.maps\" />\n\nimport { useEffect, useState } from \"react\"\nimport { setOptions, importLibrary } from \"@googlemaps/js-api-loader\"\n\n\ntype GoogleMapsLibraryName =\n | \"core\"\n | \"maps\"\n | \"maps3d\"\n | \"routes\"\n | \"geometry\"\n | \"elevation\"\n | \"streetView\"\n | \"journeySharing\"\n | \"drawing\"\n | \"visualization\"\n | \"marker\"\n | \"places\"\n | \"geocoding\"\n | (string & {})\n\ntype GoogleMapsLibraryModule<T extends GoogleMapsLibraryName> =\n T extends \"core\" ? google.maps.CoreLibrary\n : T extends \"maps\" ? google.maps.MapsLibrary\n : T extends \"maps3d\" ? google.maps.Maps3DLibrary\n : T extends \"routes\" ? google.maps.RoutesLibrary\n : T extends \"geometry\" ? google.maps.GeometryLibrary\n : T extends \"elevation\" ? google.maps.ElevationLibrary\n : T extends \"streetView\" ? google.maps.StreetViewLibrary\n : T extends \"journeySharing\" ? google.maps.JourneySharingLibrary\n : T extends \"drawing\" ? google.maps.DrawingLibrary\n : T extends \"visualization\" ? google.maps.VisualizationLibrary\n : T extends \"marker\" ? google.maps.MarkerLibrary\n : T extends \"places\" ? google.maps.PlacesLibrary\n : T extends \"geocoding\" ? google.maps.GeocodingLibrary\n : unknown\n\n\nlet initPromise: Promise<void> | null = null\nlet configuredKey: string | null = null\n\nconst libraryPromises = new Map<string, Promise<unknown>>()\n\nconst ensureGoogle = (apiKey?: string) => {\n if (!apiKey) {\n const error = new Error(\"useMapsLibrary: missing Google Maps API key\")\n console.error(error.message)\n return Promise.reject(error)\n }\n\n if (!initPromise) {\n configuredKey = apiKey\n setOptions({\n key: apiKey,\n v: \"weekly\",\n })\n initPromise = importLibrary(\"core\")\n .then(() => {})\n .catch(error => {\n initPromise = null\n throw error\n })\n } else if (configuredKey !== apiKey) {\n console.warn(\n \"useMapsLibrary: ignoring attempt to re-initialize Google Maps with a different API key than the one already in use.\"\n )\n }\n\n return initPromise\n}\n\nconst loadLibrary = <T extends GoogleMapsLibraryName>(\n library: T,\n apiKey?: string\n) => {\n const cacheKey = String(library)\n const cached = libraryPromises.get(cacheKey)\n if (cached) {\n return cached as Promise<GoogleMapsLibraryModule<T>>\n }\n\n const promise = ensureGoogle(apiKey)\n .then(() => {\n if (typeof window === \"undefined\" || !window.google?.maps?.importLibrary) {\n throw new Error(\"useMapsLibrary: google.maps.importLibrary is not available.\")\n }\n return window.google.maps.importLibrary(cacheKey)\n })\n .then(module => module as GoogleMapsLibraryModule<T>)\n .catch(error => {\n libraryPromises.delete(cacheKey)\n throw error\n })\n\n libraryPromises.set(cacheKey, promise as Promise<unknown>)\n return promise\n}\n\nexport const useMapsLibrary = <T extends GoogleMapsLibraryName>(\n library: T,\n apiKey?: string\n) => {\n const [module, setModule] =\n useState<GoogleMapsLibraryModule<T> | null>(null)\n\n useEffect(() => {\n if (typeof window === \"undefined\") {\n return\n }\n\n let cancelled = false\n\n loadLibrary(library, apiKey)\n .then(loadedModule => {\n if (!cancelled) {\n setModule(loadedModule)\n }\n })\n .catch(error => {\n if (!cancelled) {\n console.error(`useMapsLibrary: failed to load ${String(library)} library`, error)\n setModule(null)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [library, apiKey])\n\n return module\n}\n\nexport type { GoogleMapsLibraryName, GoogleMapsLibraryModule }\n"],"mappings":";;;;AAuCA,IAAI,IAAoC,MACpC,IAA+B,MAE7B,oBAAkB,IAAI,IAA8B,GAEpD,KAAgB,MAAoB;CACxC,IAAI,CAAC,GAAQ;EACX,IAAM,IAAQ,gBAAI,MAAM,6CAA6C;EAErE,OADA,QAAQ,MAAM,EAAM,OAAO,GACpB,QAAQ,OAAO,CAAK;CAC7B;CAoBA,OAlBK,IAYM,MAAkB,KAC3B,QAAQ,KACN,qHACF,KAdA,IAAgB,GAChB,EAAW;EACT,KAAK;EACL,GAAG;CACL,CAAC,GACD,IAAc,EAAc,MAAM,CAAC,CAChC,WAAW,CAAC,CAAC,CAAC,CACd,OAAM,MAAS;EAEd,MADA,IAAc,MACR;CACR,CAAC,IAOE;AACT,GAEM,KACJ,GACA,MACG;CACH,IAAM,IAAW,OAAO,CAAO,GACzB,IAAS,EAAgB,IAAI,CAAQ;CAC3C,IAAI,GACF,OAAO;CAGT,IAAM,IAAU,EAAa,CAAM,CAAC,CACjC,WAAW;EACV,IAAI,OAAO,SAAW,OAAe,CAAC,OAAO,QAAQ,MAAM,eACzD,MAAU,MAAM,6DAA6D;EAE/E,OAAO,OAAO,OAAO,KAAK,cAAc,CAAQ;CAClD,CAAC,CAAC,CACD,MAAK,MAAU,CAAoC,CAAC,CACpD,OAAM,MAAS;EAEd,MADA,EAAgB,OAAO,CAAQ,GACzB;CACR,CAAC;CAGH,OADA,EAAgB,IAAI,GAAU,CAA2B,GAClD;AACT,GAEa,KACX,GACA,MACG;eACG,CAAC,GAAQ,KACb,EAA4C,IAAI,GAExCA,GAuBPC;CAEH,OAlBuB,EAAA,OAAA,KAAA,EAAA,OAAT,KAPJ,UAAM;EACd,IAAI,OAAO,SAAW,KACpB;EAGF,IAAI,IAAY;EAehB,OAbA,EAAY,GAAS,CAAM,CAAC,CACzB,MAAK,MAAgB;GACpB,AAAK,KACH,EAAU,CAAY;EAE1B,CAAC,CAAC,CACD,OAAM,MAAS;GACd,AAAK,MACH,QAAQ,MAAM,kCAAkC,OAAO,CAAO,EAAE,WAAW,CAAK,GAChF,EAAU,IAAI;EAElB,CAAC,SAEU;GACX,IAAY;EACd;CACF,GAAG,IAAA,CAAC,GAAS,CAAM,GAhBI,EAAA,KAAA,GAAT,EAAA,KAAA,+CAPd,EAAUD,GAuBPC,CAAiB,GAEb;AACT"}