fix(settings): keep the language and save settings one after the other

This commit is contained in:
lobo committed 2026-09-29 13:27:31 +02:00
1 parent 14dd59f9e0
commit f605917874
25 files changed
+423 -98

No files matched your search

+15 -38
View File
@@ -31,6 +31,7 @@ import {coverUrl} from '../utils/games.js';
import {apiFetch, refreshSession} from '../utils/api.js';
import {hasSession, markSession} from '../utils/auth.js';
import {FAVORITE_PLATFORMS, saveFavoritePlatform} from '../utils/platforms.js';
import {LANGUAGES, saveSettings} from '../utils/settings.js';
const BACKEND_ERROR_KEY = {
DISPLAY_NAME_ALREADY_IN_USE: 'SETTINGS.ERROR_DISPLAY_NAME_IN_USE',
@@ -44,7 +45,6 @@ const STEAM_LINK_TOASTS = {
failed: {show: toast.error, key: 'SETTINGS.STEAM_LINK.FAILED'},
};
const SUPPORTED_LANGUAGES = ['en', 'de'];
// Radix Select has no empty value.
const NO_FAVORITE = 'NONE';
@@ -81,7 +81,7 @@ export default function SettingsPage() {
formState: {errors, isSubmitting},
} = useForm({
resolver: zodResolver(settingsSchema),
defaultValues: {useDarkTheme: true, language: 'en', displayName: ''},
defaultValues: {displayName: ''},
});
// A language change gives a new `t` and runs the effect again; the toast shows once.
@@ -123,42 +123,32 @@ export default function SettingsPage() {
}
if (!response.ok) {
setLoadError(t('INFO.ERROR.LOAD_PROFILE'));
setLoadError('INFO.ERROR.LOAD_PROFILE');
return;
}
const data = await response.json();
setProfile(data);
reset({
useDarkTheme: !!data.settings?.useDarkTheme,
language: data.settings?.language || 'en',
displayName: data.displayName ?? '',
});
reset({displayName: data.displayName ?? ''});
} catch (err) {
console.error('Settings load error:', err);
setLoadError(t('INFO.ERROR.NETWORK_FAILURE'));
setLoadError('INFO.ERROR.NETWORK_FAILURE');
} finally {
setIsLoading(false);
}
};
load();
}, [navigate, expireSession, reset, t, steamOutcome]);
}, [navigate, expireSession, reset, steamOutcome]);
const persistSettings = async (payload) => {
const persistSettings = async (send) => {
if (!hasSession()) {
navigate('/login');
return {ok: false};
}
try {
const response = await apiFetch(`${import.meta.env.VITE_API_URL}/api/users/settings`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const response = await send();
if (response.status === 401) {
expireSession();
@@ -181,23 +171,14 @@ export default function SettingsPage() {
}
};
// The backend replaces all settings, so every save sends the saved values of the fields it does not change.
const savedSettings = () => ({
useDarkTheme: isDarkMode,
language: i18n.language,
displayName: profile?.displayName ?? null,
publicProfile: !!profile?.settings?.publicProfile,
});
const handleDarkThemeToggle = async () => {
const next = !isDarkMode;
setDarkMode(next); // apply on the fly
await persistSettings({...savedSettings(), useDarkTheme: next});
await persistSettings(() => saveSettings({useDarkTheme: next}));
};
const handleLanguageChange = async (next) => {
setLanguage(next); // apply on the fly
await persistSettings({...savedSettings(), language: next});
await persistSettings(() => setLanguage(next));
};
const handleFavoritePlatformChange = async (value) => {
@@ -210,7 +191,7 @@ export default function SettingsPage() {
const handlePublicProfileToggle = async () => {
const next = !profile?.settings?.publicProfile;
const result = await persistSettings({...savedSettings(), publicProfile: next});
const result = await persistSettings(() => saveSettings({publicProfile: next}));
if (result.ok) {
setProfile((p) => ({...p, settings: {...p.settings, publicProfile: next}}));
}
@@ -237,12 +218,8 @@ export default function SettingsPage() {
const onSubmitProfile = async (values) => {
// Without a name the profile has no link, so removing the name also makes it private.
const publicProfile = savedSettings().publicProfile && !!values.displayName;
const result = await persistSettings({
...savedSettings(),
displayName: values.displayName,
publicProfile,
});
const publicProfile = !!profile?.settings?.publicProfile && !!values.displayName;
const result = await persistSettings(() => saveSettings({displayName: values.displayName, publicProfile}));
if (result.ok) {
setProfile((p) => (p ? {
...p, displayName: values.displayName, settings: {...p.settings, publicProfile},
@@ -262,7 +239,7 @@ export default function SettingsPage() {
}
if (loadError) {
return <div className="py-24 text-center text-destructive">{loadError}</div>;
return <div className="py-24 text-center text-destructive">{t(loadError)}</div>;
}
const publicProfile = !!profile?.settings?.publicProfile;
@@ -313,7 +290,7 @@ export default function SettingsPage() {
<SelectValue/>
</SelectTrigger>
<SelectContent>
{SUPPORTED_LANGUAGES.map((lang) => (
{LANGUAGES.map((lang) => (
<SelectItem key={lang} value={lang}>
{t(`SETTINGS.LANGUAGE_OPTIONS.${lang.toUpperCase()}`)}
</SelectItem>