feat(notifications) : read all when bell opens

This commit is contained in:
2026-10-12 00:26:15 +07:00 Unverified
parent 408ea8a219
commit 839f98aab7
+13 -4
View File
@@ -13,8 +13,8 @@ import { useSitePushDisabled } from './push-client';
type Item = { id: string; title: string; body: string; url: string; readAt: string | null };
type Feed = { items: Item[]; unread: number; nextCursor: string | null };
const empty: Feed = { items: [], unread: 0, nextCursor: null };
type State = Feed & { signedIn: boolean; busy: boolean; error: string; refresh: (more?: boolean) => Promise<void>; read: (id?: string) => Promise<void> };
const Context = createContext<State>({ ...empty, signedIn: false, busy: false, error: '', refresh: async () => {}, read: async () => {} });
type State = Feed & { signedIn: boolean; busy: boolean; error: string; refresh: (more?: boolean) => Promise<Feed | undefined>; read: (id?: string) => Promise<void> };
const Context = createContext<State>({ ...empty, signedIn: false, busy: false, error: '', refresh: async () => undefined, read: async () => {} });
export function NotificationProvider({ children }: { children: ReactNode }) {
const { data } = authClient.useSession();
const userId = data?.user.id;
@@ -26,7 +26,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
useEffect(() => { current.current = feed; }, [feed]);
const sequence = useRef(0);
const invalidate = useCallback(() => { ++sequence.current; }, []);
const refresh = useCallback(async (more = false) => {
const refresh = useCallback(async (more = false): Promise<Feed | undefined> => {
if (!userId) return;
const request = ++sequence.current;
const cursor = more ? current.current.nextCursor : null;
@@ -38,6 +38,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
if (sequence.current !== request) return;
setFeed(previous => ({ ...result, items: more ? [...previous.items, ...result.items.filter(item => !previous.items.some(old => old.id === item.id))] : result.items }));
setOwner(userId); setError('');
return result;
} catch (cause) { if (sequence.current === request) setError(cause instanceof Error ? cause.message : 'โหลดการแจ้งเตือนไม่สำเร็จ'); }
finally { if (sequence.current === request) setBusy(false); }
}, [userId]);
@@ -63,7 +64,15 @@ export function NotificationBell() {
const state = useContext(Context);
const disabled = useSitePushDisabled();
const [open, setOpen] = useState(false);
return <Popover open={open} onOpenChange={value => { setOpen(value); if (value) void state.refresh(); }}>
return <Popover open={open} onOpenChange={value => {
setOpen(value);
if (value) {
if (state.unread > 0) void state.read().catch(() => undefined);
else void state.refresh().then(result => {
if (result?.unread) void state.read().catch(() => undefined);
});
}
}}>
<PopoverTrigger render={<Button variant="ghost" size="icon" className="relative shrink-0" aria-label={`การแจ้งเตือน${disabled ? ' ปิดชั่วคราว' : state.unread ? ` ใหม่ ${state.unread} รายการ` : ''}`} />}>{disabled ? <BellOffIcon /> : <BellIcon />}
{!disabled && state.unread > 0 && <Badge variant="destructive" className="absolute -right-1 -top-1 min-w-5 justify-center rounded-full bg-destructive px-1 text-background dark:bg-destructive">{state.unread > 9 ? '9+' : state.unread}</Badge>}
</PopoverTrigger>