Scrum4Me/components/shared/user-settings-bridge.tsx
Madhura68 bf6bdf366f feat(PBI-76): bridge runs one-shot localStorage migration
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>
2026-05-10 12:50:50 +02:00

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
}