diff --git a/src/frontend/src/components/AppInitialization.tsx b/src/frontend/src/components/AppInitialization.tsx index f4d37f69..b99952f6 100644 --- a/src/frontend/src/components/AppInitialization.tsx +++ b/src/frontend/src/components/AppInitialization.tsx @@ -3,12 +3,14 @@ import { useConfig } from '@/api/useConfig' import { useAnalytics } from '@/features/analytics/hooks/useAnalytics' import { useSupport } from '@/features/support/hooks/useSupport' import { useLocation } from 'wouter' +import { useSyncUserPreferencesWithBackend } from '@/features/auth' const SDK_BASE_ROUTE = '/sdk' export const AppInitialization = () => { const { data } = useConfig() const [location] = useLocation() + useSyncUserPreferencesWithBackend() const { analytics = {}, diff --git a/src/frontend/src/features/auth/api/ApiUser.ts b/src/frontend/src/features/auth/api/ApiUser.ts index 716528c4..5d1e16a1 100644 --- a/src/frontend/src/features/auth/api/ApiUser.ts +++ b/src/frontend/src/features/auth/api/ApiUser.ts @@ -1,6 +1,10 @@ +import { BackendLanguage } from '@/utils/languages' + export type ApiUser = { id: string email: string full_name: string last_name: string + language: BackendLanguage + timezone: string } diff --git a/src/frontend/src/features/auth/api/updateUserPreferences.ts b/src/frontend/src/features/auth/api/updateUserPreferences.ts new file mode 100644 index 00000000..09d13863 --- /dev/null +++ b/src/frontend/src/features/auth/api/updateUserPreferences.ts @@ -0,0 +1,15 @@ +import { type ApiUser } from './ApiUser' +import { fetchApi } from '@/api/fetchApi' + +export type ApiUserPreferences = Pick + +export const updateUserPreferences = async ({ + user, +}: { + user: ApiUserPreferences +}): Promise => { + return await fetchApi(`/users/${user.id}/`, { + method: 'PUT', + body: JSON.stringify({ timezone: user.timezone, language: user.language }), + }) +} diff --git a/src/frontend/src/features/auth/api/useSyncUserPreferencesWithBackend.tsx b/src/frontend/src/features/auth/api/useSyncUserPreferencesWithBackend.tsx new file mode 100644 index 00000000..ed06e816 --- /dev/null +++ b/src/frontend/src/features/auth/api/useSyncUserPreferencesWithBackend.tsx @@ -0,0 +1,47 @@ +import { useMutation } from '@tanstack/react-query' +import { keys } from '@/api/queryKeys' +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { queryClient } from '@/api/queryClient' +import { updateUserPreferences } from './updateUserPreferences' +import { convertToBackendLanguage } from '@/utils/languages' +import { useUser } from './useUser' + +/** + * Hook that synchronizes user browser preferences (language, timezone) with backend user settings. + * Automatically updates backend when browser settings change for logged-in users. + */ +export const useSyncUserPreferencesWithBackend = () => { + const { i18n } = useTranslation() + const { user, isLoggedIn } = useUser() + + const { mutateAsync } = useMutation({ + mutationFn: updateUserPreferences, + onSuccess: (updatedUser) => { + queryClient.setQueryData([keys.user], updatedUser) + }, + }) + + useEffect(() => { + if (!user || !isLoggedIn) return + + const syncBrowserPreferencesToBackend = async () => { + const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone + const currentLanguage = convertToBackendLanguage(i18n.language) + if ( + currentLanguage !== user.language || + currentTimezone !== user.timezone + ) { + await mutateAsync({ + user: { + id: user.id, + timezone: currentTimezone, + language: currentLanguage, + }, + }) + } + } + + syncBrowserPreferencesToBackend() + }, [i18n.language, isLoggedIn, user, mutateAsync]) +} diff --git a/src/frontend/src/features/auth/index.ts b/src/frontend/src/features/auth/index.ts index a3446c50..710aa9ed 100644 --- a/src/frontend/src/features/auth/index.ts +++ b/src/frontend/src/features/auth/index.ts @@ -1,3 +1,4 @@ export { useUser } from './api/useUser' +export { useSyncUserPreferencesWithBackend } from './api/useSyncUserPreferencesWithBackend' export { authUrl } from './utils/authUrl' export { UserAware } from './components/UserAware' diff --git a/src/frontend/src/utils/languages.ts b/src/frontend/src/utils/languages.ts new file mode 100644 index 00000000..38a9c540 --- /dev/null +++ b/src/frontend/src/utils/languages.ts @@ -0,0 +1,16 @@ +// Map frontend language codes to backend language codes + +export type BackendLanguage = 'en-us' | 'fr-fr' | 'nl-nl' +export type FrontendLanguage = 'en' | 'fr' | 'nl' + +const frontendToBackendMap: Record = { + en: 'en-us', + fr: 'fr-fr', + nl: 'nl-nl', +} + +export const convertToBackendLanguage = ( + frontendLang: string = 'fr' +): BackendLanguage => { + return frontendToBackendMap[frontendLang as FrontendLanguage] +}