From ba405e606b7e50a43cafb923c19c49b06d9fb566 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 26 Oct 2026 11:10:00 -0400 Subject: [PATCH] feat: Display dedicated notifications section for seriouscritter Co-authored-by: aider (ollama_chat/hf.co/TrevorJS/gemma-4-E4B-it-uncensored-GGUF:Q4_K_M) --- apps/api/src/services/notifications.ts | 22 +++++++++--- apps/web/src/app/notifications/page.tsx | 47 ++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/apps/api/src/services/notifications.ts b/apps/api/src/services/notifications.ts index 30b075a..985ae7a 100644 --- a/apps/api/src/services/notifications.ts +++ b/apps/api/src/services/notifications.ts @@ -103,15 +103,27 @@ export async function toNotificationDTOs(rows: Row[]): Promise { const n = schema.notifications; const before = cursor ? new Date(cursor) : null; - const rows = await db + const query = db .select() .from(n) - .where(and(eq(n.userId, userId), before && !isNaN(before.getTime()) ? lt(n.createdAt, before) : undefined)) - .orderBy(desc(n.createdAt)) - .limit(31); + .where(and( + eq(n.userId, userId), + before && !isNaN(before.getTime()) ? lt(n.createdAt, before) : undefined, + actorId ? eq(n.actorId, actorId) : undefined // Apply actor filter if provided + )); + + const rows = await query.orderBy(desc(n.createdAt)).limit(31); const page = rows.slice(0, 30); return { notifications: await toNotificationDTOs(page), diff --git a/apps/web/src/app/notifications/page.tsx b/apps/web/src/app/notifications/page.tsx index d19fcad..a6045ca 100644 --- a/apps/web/src/app/notifications/page.tsx +++ b/apps/web/src/app/notifications/page.tsx @@ -8,32 +8,68 @@ import { notificationPath, notificationText, timeAgo, type NotificationListDTO } import { Avatar } from '@/components/Avatar'; import { client, useMe } from '@/lib/api'; +const ADMIN_ACTOR_USERNAME = 'seriouscritter'; + /** Reactions, comments, new followers and songs sent to you, newest first. Opening it marks them read. */ export default function NotificationsPage() { const { me, loggedOut } = useMe(); const { mutate } = useSWRConfig(); + + // Fetch all notifications for the user const { data, size, setSize, isLoading } = useSWRInfinite( (i, prev: NotificationListDTO | null) => (!me || (i > 0 && !prev?.nextCursor) ? null : ['notifications', prev?.nextCursor ?? null]), ([, cursor]: [string, string | null]) => client.notifications(cursor), { revalidateOnFocus: false }, ); - const items = data?.flatMap((p) => p.notifications) ?? []; + + const allItems = data?.flatMap((p) => p.notifications) ?? []; const unread = data?.[0]?.unread ?? 0; + // Filter messages specifically from seriouscritter + const seriousCritterMessages = allItems.filter(n => n.actor?.username === ADMIN_ACTOR_USERNAME); + // Seen once you've looked: clear the bell (rows stay highlighted until you come back). useEffect(() => { if (unread) client.markNotificationsRead().then(() => mutate('/notifications'), () => {}); }, [unread, mutate]); if (loggedOut) return

Log in to see your notifications.

; + return (

Notifications

- {items.length === 0 && !isLoading && ( + + {/* Dedicated section for seriouscritter's messages */} + {seriousCritterMessages.length > 0 && ( +
+

From {ADMIN_ACTOR_USERNAME}

+
    + {seriousCritterMessages.map((n) => { + const { title, body } = notificationText(n); + return ( +
  • + + + + {title} + {body + {timeAgo(n.createdAt)} + + {n.track?.artworkUrl && } + +
  • + ); + })} +
+
+ )} + + {/* General notifications list */} + {allItems.length === 0 && !isLoading && (

Nothing yet. Reactions, new followers and songs sent to you show up here.

)}
    - {items.map((n) => { + {allItems.map((n) => { const { title, body } = notificationText(n); return (
  • @@ -41,7 +77,7 @@ export default function NotificationsPage() { {title} - {body} + {body {timeAgo(n.createdAt)} {n.track?.artworkUrl && } @@ -50,11 +86,12 @@ export default function NotificationsPage() { ); })}
+ {data?.at(-1)?.nextCursor && ( )} -
+ ); }