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:
parent
bcb940f3f1
commit
f5d294abbf
1 changed files with 44 additions and 3 deletions
|
|
@ -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 { 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 { shouldShowRoleSwitcher, getRoleLabel } from '~/lib/auth-flow';
|
||||||
import {
|
import {
|
||||||
getRoleTourStorageKey,
|
getRoleTourStorageKey,
|
||||||
|
|
@ -340,6 +340,22 @@ export default function DashboardLayout(props: { children: any }) {
|
||||||
const [tourKind, setTourKind] = createSignal<GuidedTourKind | null>(null);
|
const [tourKind, setTourKind] = createSignal<GuidedTourKind | null>(null);
|
||||||
const [tourStepIndex, setTourStepIndex] = createSignal(0);
|
const [tourStepIndex, setTourStepIndex] = createSignal(0);
|
||||||
const [authReady, setAuthReady] = createSignal(false);
|
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
|
// Restore session or inject mock preview config
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
@ -350,6 +366,10 @@ export default function DashboardLayout(props: { children: any }) {
|
||||||
await bootstrapAuth();
|
await bootstrapAuth();
|
||||||
}
|
}
|
||||||
setAuthReady(true);
|
setAuthReady(true);
|
||||||
|
// Poll for unread notification count every 60 seconds
|
||||||
|
await fetchUnreadCount();
|
||||||
|
const notifTimer = setInterval(fetchUnreadCount, 60_000);
|
||||||
|
onCleanup(() => clearInterval(notifTimer));
|
||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
|
@ -580,8 +600,29 @@ export default function DashboardLayout(props: { children: any }) {
|
||||||
</A>
|
</A>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</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 />
|
<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>
|
</A>
|
||||||
<div class="topbar-user">
|
<div class="topbar-user">
|
||||||
<span class="topbar-name">{rc()?.user?.full_name ?? 'User'}</span>
|
<span class="topbar-name">{rc()?.user?.full_name ?? 'User'}</span>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue