refactor(frontend): lint, one session guard, one request helper, one user context

This commit is contained in:
lobo committed 2026-09-29 15:40:55 +02:00
1 parent 111ab1e200
commit d0ea9e900d
78 files changed
+2138 -624

No files matched your search

+20 -60
View File
@@ -1,5 +1,5 @@
import {useEffect, useRef, useState} from 'react';
import {useNavigate, useSearchParams} from 'react-router-dom';
import {useSearchParams} from 'react-router-dom';
import {useTranslation} from 'react-i18next';
import {useForm} from 'react-hook-form';
import {zodResolver} from '@hookform/resolvers/zod';
@@ -20,7 +20,7 @@ import FormField from '../components/FormField.jsx';
import SteamAccountCard from '../components/SteamAccountCard.jsx';
import {settingsSchema} from '../utils/validation.js';
import {usePreferences} from '../context/PreferencesContext.jsx';
import useSessionExpired from '../hooks/useSessionExpired.js';
import {useCurrentUserContext} from '../context/CurrentUserContext.jsx';
import useSaveAppearance from '../hooks/useSaveAppearance.js';
import {AccentPicker, CornersPicker} from '../components/StylePickers.jsx';
import ProfileStyleSettings from '../components/ProfileStyleSettings.jsx';
@@ -28,8 +28,8 @@ import {normalizeProfileStyle} from '../utils/profileStyle.js';
import useSaveProfileStyle from '../hooks/useSaveProfileStyle.js';
import useSaveResponse from '../hooks/useSaveResponse.js';
import {coverUrl} from '../utils/games.js';
import {apiFetch, refreshSession} from '../utils/api.js';
import {hasSession, markSession} from '../utils/auth.js';
import {refreshSession} from '../utils/api.js';
import {markSession} from '../utils/auth.js';
import {FAVORITE_PLATFORMS, saveFavoritePlatform} from '../utils/platforms.js';
import {LANGUAGES, saveSettings} from '../utils/settings.js';
import {MAX_DISPLAY_NAME_LENGTH} from '../utils/limits.js';
@@ -60,14 +60,12 @@ const formatDate = (value, locale) => {
export default function SettingsPage() {
const {t, i18n} = useTranslation();
const navigate = useNavigate();
const expireSession = useSessionExpired();
const {isDarkMode, setDarkMode, setLanguage, appearance} = usePreferences();
const saveAppearance = useSaveAppearance();
const saveStyle = useSaveProfileStyle();
const runSave = useSaveResponse();
const [profile, setProfile] = useState(null);
const {user: profile, updateUser: updateProfile, reload: reloadUser} = useCurrentUserContext();
const [isLoading, setIsLoading] = useState(true);
const [loadError, setLoadError] = useState(null);
const [savingProfileStyle, setSavingProfileStyle] = useState(false);
@@ -99,62 +97,26 @@ export default function SettingsPage() {
}, [steamOutcome, setSearchParams, t]);
useEffect(() => {
if (!hasSession()) {
navigate('/login');
return;
}
const load = async () => {
try {
if (steamOutcome === 'linked') {
// Refresh reads the new Steam ID from the database, so Core gets it in the token at once.
await refreshSession().catch(() => false);
markSession();
}
const response = await apiFetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.status === 401) {
expireSession();
return;
}
if (!response.ok) {
setLoadError('INFO.ERROR.LOAD_PROFILE');
return;
}
const data = await response.json();
setProfile(data);
reset({displayName: data.displayName ?? ''});
} catch (err) {
console.error('Settings load error:', err);
setLoadError('INFO.ERROR.NETWORK_FAILURE');
} finally {
setIsLoading(false);
if (steamOutcome === 'linked') {
// Refresh reads the new Steam ID from the database, so Core gets it in the token at once.
await refreshSession().catch(() => false);
markSession();
}
const {ok, status, data} = await reloadUser();
if (ok) reset({displayName: data.displayName ?? ''});
else if (status !== 401) setLoadError(status === 0 ? 'INFO.ERROR.NETWORK_FAILURE' : 'INFO.ERROR.LOAD_PROFILE');
setIsLoading(false);
};
load();
}, [navigate, expireSession, reset, steamOutcome]);
}, [reloadUser, reset, steamOutcome]);
const persistSettings = async (send) => {
if (!hasSession()) {
navigate('/login');
return {ok: false};
}
try {
const response = await send();
if (response.status === 401) {
expireSession();
return {ok: false};
}
if (response.status === 401) return {ok: false};
if (!response.ok) {
const data = await response.json().catch(() => ({}));
@@ -185,7 +147,7 @@ export default function SettingsPage() {
const handleFavoritePlatformChange = async (value) => {
const platform = value === NO_FAVORITE ? null : value;
if (await runSave(() => saveFavoritePlatform(platform))) {
setProfile((p) => ({...p, settings: {...p.settings, favoritePlatform: platform}}));
updateProfile((p) => ({...p, settings: {...p.settings, favoritePlatform: platform}}));
toast.success(t('SETTINGS.SAVED'));
}
};
@@ -194,14 +156,14 @@ export default function SettingsPage() {
const next = !profile?.settings?.publicProfile;
const result = await persistSettings(() => saveSettings({publicProfile: next}));
if (result.ok) {
setProfile((p) => ({...p, settings: {...p.settings, publicProfile: next}}));
updateProfile((p) => ({...p, settings: {...p.settings, publicProfile: next}}));
}
};
// Shown at once; the pickers wait for the answer, so the saves reach Auth one after the other.
const handleProfileStyleChange = async (changes) => {
const previous = normalizeProfileStyle(profile?.settings?.profileStyle);
const showProfileStyle = (next) => setProfile((p) => ({...p, settings: {...p.settings, profileStyle: next}}));
const showProfileStyle = (next) => updateProfile((p) => ({...p, settings: {...p.settings, profileStyle: next}}));
showProfileStyle({...previous, ...changes});
setSavingProfileStyle(true);
showProfileStyle(await saveStyle(changes, previous) ?? previous);
@@ -222,9 +184,7 @@ export default function SettingsPage() {
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},
} : p));
updateProfile((p) => ({...p, displayName: values.displayName, settings: {...p.settings, publicProfile}}));
}
};
@@ -382,7 +342,7 @@ export default function SettingsPage() {
</Card>
<SteamAccountCard steamId={profile?.steamId} verified={!!profile?.steamIdVerified}
onUnlinked={() => setProfile((p) => ({...p, steamId: null, steamIdVerified: false}))}/>
onUnlinked={() => updateProfile((p) => ({...p, steamId: null, steamIdVerified: false}))}/>
<Card>
<CardHeader>