Add unread notification badge and poll to dashboard layout

- Import onCleanup, getAuthHeader in DashboardLayout
- Poll /api/me/notifications every 60s for unread_count
- Show orange badge on bell icon when unread > 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-04-02 18:13:14 +02:00
parent bcb940f3f1
commit f5d294abbf

View file

@ -1,6 +1,6 @@
import { Component, Show, createEffect, For, createSignal, onMount } from 'solid-js';
import { Component, Show, createEffect, For, createSignal, onMount, onCleanup } from 'solid-js';
import { useNavigate, A, useSearchParams } from '@solidjs/router';
import { authState, logout, switchRole, bootstrapAuth, setMockRuntimeConfig, isModuleLocked } from '~/lib/auth';
import { authState, logout, switchRole, bootstrapAuth, setMockRuntimeConfig, isModuleLocked, getAuthHeader } from '~/lib/auth';
import { shouldShowRoleSwitcher, getRoleLabel } from '~/lib/auth-flow';
import {
getRoleTourStorageKey,
@ -340,6 +340,22 @@ export default function DashboardLayout(props: { children: any }) {
const [tourKind, setTourKind] = createSignal<GuidedTourKind | null>(null);
const [tourStepIndex, setTourStepIndex] = createSignal(0);
const [authReady, setAuthReady] = createSignal(false);
const [unreadCount, setUnreadCount] = createSignal(0);
const API = import.meta.env.VITE_API_URL ?? 'http://localhost:8000';
async function fetchUnreadCount() {
const auth = getAuthHeader();
if (!auth.Authorization) return;
try {
const res = await fetch(`${API}/api/me/notifications?page=1&limit=1`, { headers: auth });
if (!res.ok) return;
const data = await res.json();
setUnreadCount(data.unread_count ?? 0);
} catch {
// ignore
}
}
// Restore session or inject mock preview config
onMount(async () => {
@ -350,6 +366,10 @@ export default function DashboardLayout(props: { children: any }) {
await bootstrapAuth();
}
setAuthReady(true);
// Poll for unread notification count every 60 seconds
await fetchUnreadCount();
const notifTimer = setInterval(fetchUnreadCount, 60_000);
onCleanup(() => clearInterval(notifTimer));
});
createEffect(() => {
@ -580,8 +600,29 @@ export default function DashboardLayout(props: { children: any }) {
</A>
</div>
</Show>
<A href="/dashboard/notifications" class="topbar-icon-btn" title="Notifications">
<A href="/dashboard/notifications" class="topbar-icon-btn" title="Notifications" style={{ position: 'relative' }}>
<IconBell />
<Show when={unreadCount() > 0}>
<span style={{
position: 'absolute',
top: '2px',
right: '2px',
'min-width': '16px',
height: '16px',
'border-radius': '999px',
background: '#fd6116',
color: '#fff',
'font-size': '10px',
'font-weight': '800',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
padding: '0 3px',
'line-height': '1',
}}>
{unreadCount() > 99 ? '99+' : unreadCount()}
</span>
</Show>
</A>
<div class="topbar-user">
<span class="topbar-name">{rc()?.user?.full_name ?? 'User'}</span>