diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index 9271c4c..424f323 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -8,6 +8,8 @@ import { MinWidthBanner } from '@/components/shared/min-width-banner' import { StatusBar } from '@/components/shared/status-bar' import { SoloRealtimeBridge } from '@/components/solo/realtime-bridge' import { NotificationsBridge } from '@/components/notifications/notifications-bridge' +import { UserSettingsBridge } from '@/components/shared/user-settings-bridge' +import { parseUserSettings } from '@/lib/user-settings' import { AlertToast } from '@/components/shared/alert-toast' import { Suspense } from 'react' @@ -17,7 +19,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod const [user, userRoles, accessibleProducts] = await Promise.all([ prisma.user.findUnique({ where: { id: session.userId }, - select: { username: true, email: true, active_product_id: true, min_quota_pct: true }, + select: { username: true, email: true, active_product_id: true, min_quota_pct: true, settings: true }, }), prisma.userRole.findMany({ where: { user_id: session.userId }, @@ -79,6 +81,10 @@ export default async function AppLayout({ children }: { children: React.ReactNod + diff --git a/components/shared/user-settings-bridge.tsx b/components/shared/user-settings-bridge.tsx new file mode 100644 index 0000000..6a8740e --- /dev/null +++ b/components/shared/user-settings-bridge.tsx @@ -0,0 +1,41 @@ +'use client' + +import { useEffect } from 'react' +import { useUserSettingsStore } from '@/stores/user-settings/store' +import type { UserSettings } from '@/lib/user-settings' + +interface Props { + initial: UserSettings + isDemo: boolean +} + +/** + * PBI-76: hydrates the user-settings Zustand store with server-rendered prefs + * and opens an SSE subscription so other tabs/devices of the same user + * immediately see changes. Demo accounts skip the SSE subscription — their + * settings live only in-memory. + */ +export function UserSettingsBridge({ initial, isDemo }: Props) { + const hydrate = useUserSettingsStore((s) => s.hydrate) + const applyServerPatch = useUserSettingsStore((s) => s.applyServerPatch) + + useEffect(() => { + hydrate(initial, isDemo) + }, [hydrate, initial, isDemo]) + + useEffect(() => { + if (isDemo) return + const es = new EventSource('/api/realtime/user-settings') + es.onmessage = (e) => { + try { + const patch = JSON.parse(e.data) as Partial + applyServerPatch(patch) + } catch { + // ignore malformed event + } + } + return () => es.close() + }, [applyServerPatch, isDemo]) + + return null +}