From fc128b7178c93377cc559bbde50ed423346c5bb7 Mon Sep 17 00:00:00 2001 From: HorizonCode Date: Tue, 3 Nov 2026 18:33:19 +0100 Subject: [PATCH] feat(notifications): show full notification details with actions Long announcements were clamped in the list and had nowhere to open, so notifications now open a detail dialog with the full message, type, theme, sender and timestamp, plus a shortcut into the Community Workshop. --- .../notifications/NotificationHub.svelte | 173 +++++++++++++++++- src/lib/notifications.ts | 1 + src/screens/Launch.svelte | 7 +- 3 files changed, 173 insertions(+), 8 deletions(-) diff --git a/src/lib/components/notifications/NotificationHub.svelte b/src/lib/components/notifications/NotificationHub.svelte index 287254a..eb09525 100644 --- a/src/lib/components/notifications/NotificationHub.svelte +++ b/src/lib/components/notifications/NotificationHub.svelte @@ -11,6 +11,8 @@ import { Bell, CheckCheck, + ChevronRight, + ExternalLink, LoaderCircle, Megaphone, RefreshCw, @@ -19,6 +21,8 @@ } from '@lucide/svelte'; import { Portal } from 'bits-ui'; + let { onOpenWorkshop }: { onOpenWorkshop?: () => void } = $props(); + let open = $state(false); let loading = $state(false); let loadingMore = $state(false); @@ -27,8 +31,21 @@ let unreadCount = $state(0); let hasMore = $state(false); let error = $state(null); + let selectedNotification = $state(null); const unreadLabel = $derived(unreadCount > 9 ? '9+' : String(unreadCount)); + const NOTIFICATION_TYPE_LABELS: Record = { + theme_hidden: 'Theme hidden', + theme_restored: 'Theme restored', + theme_taken_down: 'Theme taken down', + report_resolved: 'Report resolved', + report_dismissed: 'Report dismissed', + system: 'Announcement', + }; + + function notificationTypeLabel(type: string): string { + return NOTIFICATION_TYPE_LABELS[type] ?? 'Notification'; + } $effect(() => { const userId = $currentUser?.id; @@ -75,6 +92,7 @@ function openHub() { if (!$currentUser) return; + selectedNotification = null; open = true; void loadNotifications(); } @@ -105,9 +123,13 @@ if (notification.readAt) return; try { await markNotificationRead(notification.id); + const readAt = new Date().toISOString(); notifications = notifications.map((item) => - item.id === notification.id ? { ...item, readAt: new Date().toISOString() } : item + item.id === notification.id ? { ...item, readAt } : item ); + if (selectedNotification?.id === notification.id) { + selectedNotification = { ...selectedNotification, readAt }; + } unreadCount = Math.max(0, unreadCount - 1); } catch (err) { error = err instanceof Error ? err.message : String(err); @@ -124,6 +146,12 @@ ...item, readAt: item.readAt ?? now, })); + if (selectedNotification) { + selectedNotification = { + ...selectedNotification, + readAt: selectedNotification.readAt ?? now, + }; + } unreadCount = 0; } catch (err) { error = err instanceof Error ? err.message : String(err); @@ -132,6 +160,19 @@ } } + function openNotification(notification: UserNotification) { + error = null; + selectedNotification = notification; + void markRead(notification); + } + + function openWorkshopFromNotification() { + if (!selectedNotification?.themeId || !onOpenWorkshop) return; + onOpenWorkshop(); + selectedNotification = null; + open = false; + } + function formatDate(value: string): string { const date = new Date(value); return Number.isNaN(date.getTime()) ? value : date.toLocaleString(); @@ -260,10 +301,10 @@ {#each notifications as notification (notification.id)} @@ -320,5 +368,116 @@ {/if} + {#if selectedNotification} + +
+ +
+ {/if} {/if} diff --git a/src/lib/notifications.ts b/src/lib/notifications.ts index be63343..640774f 100644 --- a/src/lib/notifications.ts +++ b/src/lib/notifications.ts @@ -16,6 +16,7 @@ export interface UserNotification { themeId: string | null; themeName: string | null; actorId: number | null; + actorName?: string | null; readAt: string | null; createdAt: string; } diff --git a/src/screens/Launch.svelte b/src/screens/Launch.svelte index 857e8cc..2da5e60 100644 --- a/src/screens/Launch.svelte +++ b/src/screens/Launch.svelte @@ -2178,7 +2178,12 @@
- + { + selectedView = 'THEMES'; + activeThemesTab = 'workshop'; + }} + />