After hydrate, scans legacy localStorage keys via buildMigrationPatch and, if any data is found, pushes one bulk patch to the server, applies it locally, then removes the legacy keys. Demo accounts skip the migration entirely. Cancellable on unmount to avoid setState on unmounted component. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2 KiB
TypeScript
69 lines
2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect } from 'react'
|
|
import { updateUserSettingsAction } from '@/actions/user-settings'
|
|
import { useUserSettingsStore } from '@/stores/user-settings/store'
|
|
import type { UserSettings } from '@/lib/user-settings'
|
|
import {
|
|
buildMigrationPatch,
|
|
clearLegacyLocalStorage,
|
|
} from '@/lib/user-settings-migration'
|
|
|
|
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])
|
|
|
|
// One-shot migration: read legacy localStorage prefs, push to server, clear.
|
|
// Idempotent via marker; demo accounts skip (no server-write).
|
|
useEffect(() => {
|
|
if (isDemo) return
|
|
const result = buildMigrationPatch()
|
|
if (!result.hasData) {
|
|
clearLegacyLocalStorage([])
|
|
return
|
|
}
|
|
let cancelled = false
|
|
void (async () => {
|
|
const res = await updateUserSettingsAction(result.patch)
|
|
if (cancelled) return
|
|
if ('success' in res && res.success) {
|
|
applyServerPatch(result.patch)
|
|
clearLegacyLocalStorage(result.legacyKeys)
|
|
}
|
|
})()
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [isDemo, applyServerPatch])
|
|
|
|
useEffect(() => {
|
|
if (isDemo) return
|
|
const es = new EventSource('/api/realtime/user-settings')
|
|
es.onmessage = (e) => {
|
|
try {
|
|
const patch = JSON.parse(e.data) as Partial<UserSettings>
|
|
applyServerPatch(patch)
|
|
} catch {
|
|
// ignore malformed event
|
|
}
|
|
}
|
|
return () => es.close()
|
|
}, [applyServerPatch, isDemo])
|
|
|
|
return null
|
|
}
|