diff --git a/src/frontend/src/features/home/components/CreateMeetingMenu.tsx b/src/frontend/src/features/home/components/CreateMeetingMenu.tsx index 194f8924..99ab3e9a 100644 --- a/src/frontend/src/features/home/components/CreateMeetingMenu.tsx +++ b/src/frontend/src/features/home/components/CreateMeetingMenu.tsx @@ -9,10 +9,11 @@ import { useState } from 'react' import { menuRecipe } from '@/primitives/menuRecipe' import { ApiRoom } from '@/features/rooms/api/ApiRoom' -import { loadUserChoices } from '@livekit/components-core' +import { useSnapshot } from 'valtio' +import { userStore } from '@/stores/user' export const CreateMeetingMenu = () => { - const { username } = loadUserChoices() + const { username } = useSnapshot(userStore) const { t } = useTranslation('home') const { mutateAsync: createRoom } = useCreateRoom() diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx index 27ee3c10..a404ce3d 100644 --- a/src/frontend/src/features/rooms/components/Conference.tsx +++ b/src/frontend/src/features/rooms/components/Conference.tsx @@ -36,6 +36,7 @@ import { PictureInPictureConference } from '@/features/pip/components/PictureInP import { notifyAutoMutedOnJoin } from '@/features/notifications/utils' import { useSnapshot } from 'valtio' import { userPreferencesStore } from '@/stores/userPreferences' +import { userStore } from '@/stores/user' export const Conference = ({ roomId, @@ -53,6 +54,8 @@ export const Conference = ({ userChoices: LocalUserChoices } + const { username } = useSnapshot(userStore) + useEffect(() => { posthog.capture('visit-room', { slug: roomId }) }, [roomId, posthog]) @@ -83,10 +86,10 @@ export const Conference = ({ queryFn: () => fetchRoom({ roomId: roomId as string, - username: userConfig.username, + username: username, }).catch((error) => { if (error.statusCode == '404') { - createRoom({ slug: roomId, username: userConfig.username }) + createRoom({ slug: roomId, username }) } }), retry: false, diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx index b078cab6..606d96f3 100644 --- a/src/frontend/src/features/rooms/components/Join.tsx +++ b/src/frontend/src/features/rooms/components/Join.tsx @@ -42,12 +42,13 @@ import { saveAudioInputDeviceId, saveAudioInputEnabled, saveAudioOutputDeviceId, - saveUsername, saveVideoInputDeviceId, saveVideoInputEnabled, userChoicesStore, } from '@/stores/userChoices' +import { saveUsername, userStore } from '@/stores/user' + import { useCannotUseDevice } from '../livekit/hooks/useCannotUseDevice' import { useSnapshot } from 'valtio' @@ -121,9 +122,10 @@ export const Join = ({ audioOutputDeviceId, videoDeviceId, processorConfig, - username, } = useSnapshot(userChoicesStore) + const { username } = useSnapshot(userStore) + const initialUserChoices = useRef(null) if (initialUserChoices.current === null) { @@ -134,7 +136,6 @@ export const Join = ({ audioOutputDeviceId, videoDeviceId, processorConfig, - username, } } diff --git a/src/frontend/src/features/settings/components/tabs/AccountTab.tsx b/src/frontend/src/features/settings/components/tabs/AccountTab.tsx index 3f419fce..9cc59fc2 100644 --- a/src/frontend/src/features/settings/components/tabs/AccountTab.tsx +++ b/src/frontend/src/features/settings/components/tabs/AccountTab.tsx @@ -8,7 +8,7 @@ import { HStack } from '@/styled-system/jsx' import { useState } from 'react' import { LoginButton } from '@/components/LoginButton' import { useRenameParticipant } from '@/features/rooms/api/renameParticipant' -import { saveUsername } from '@/stores/userChoices' +import { saveUsername } from '@/stores/user' import { logout } from '@/features/auth/utils/logout' export type AccountTabProps = Pick & diff --git a/src/frontend/src/stores/user.ts b/src/frontend/src/stores/user.ts new file mode 100644 index 00000000..7bd2b16e --- /dev/null +++ b/src/frontend/src/stores/user.ts @@ -0,0 +1,37 @@ +import { proxy, subscribe } from 'valtio' +import { STORAGE_KEYS } from '@/utils/storageKeys' + +type State = { + username: string +} + +const DEFAULT_STATE = { + username: '', +} + +function getUserState(): State { + try { + const stored = localStorage.getItem(STORAGE_KEYS.USER) + if (!stored) return DEFAULT_STATE + const parsed = JSON.parse(stored) + return { + ...parsed, + } + } catch (error: unknown) { + console.error( + '[UserPreferencesStore] Failed to parse stored settings:', + error + ) + return DEFAULT_STATE + } +} + +export const userStore = proxy(getUserState()) + +subscribe(userStore, () => { + localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(userStore)) +}) + +export const saveUsername = (username: string) => { + userStore.username = username +} diff --git a/src/frontend/src/stores/userChoices.ts b/src/frontend/src/stores/userChoices.ts index bf3c0c2b..de64a07d 100644 --- a/src/frontend/src/stores/userChoices.ts +++ b/src/frontend/src/stores/userChoices.ts @@ -12,7 +12,7 @@ import { VideoQuality } from 'livekit-client' export type VideoResolution = 'h720' | 'h360' | 'h180' -export type LocalUserChoices = LocalUserChoicesLK & { +export type LocalUserChoices = Omit & { processorConfig?: ProcessorConfig noiseReductionEnabled?: boolean audioOutputDeviceId?: string @@ -32,7 +32,13 @@ function getUserChoicesState(): LocalUserChoices { export const userChoicesStore = proxy(getUserChoicesState()) subscribe(userChoicesStore, () => { - saveUserChoices(userChoicesStore, false) + // TEMPORARY: cast needed because our store omits `username`, which we no + // longer persis in this store, while LiveKit's `saveUserChoices` still expects the full + // `LocalUserChoices` shape. `username` ends up `undefined` in the saved + // object, which `saveUserChoices` tolerates at runtime. + // We are migrating away from LiveKit's persistence logic to our own store + // handling for more control — this cast can be removed once that lands. + saveUserChoices(userChoicesStore as LocalUserChoicesLK, false) }) // we run some logic on store loading to check if the processor config is still valid @@ -90,10 +96,6 @@ export const saveVideoSubscribeQuality = (quality: VideoQuality) => { userChoicesStore.videoSubscribeQuality = quality } -export const saveUsername = (username: string) => { - userChoicesStore.username = username -} - export const saveNoiseReductionEnabled = (enabled: boolean) => { userChoicesStore.noiseReductionEnabled = enabled } diff --git a/src/frontend/src/utils/storageKeys.tsx b/src/frontend/src/utils/storageKeys.tsx index 6dc10523..7c39c68c 100644 --- a/src/frontend/src/utils/storageKeys.tsx +++ b/src/frontend/src/utils/storageKeys.tsx @@ -4,5 +4,6 @@ export const STORAGE_KEYS = { NOTIFICATIONS: 'app_notification_settings', USER_PREFERENCES: 'app_user_preferences', + USER: 'app_user', ACCESSIBILITY: 'app_accessibility_settings', } as const