import {useEffect, useState} from 'react'; import {useNavigate} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; import {useForm} from 'react-hook-form'; import {zodResolver} from '@hookform/resolvers/zod'; import {Gamepad2, Loader2, Save, SlidersHorizontal, UserRound} from 'lucide-react'; import {toast} from 'sonner'; import {Button} from '@/components/ui/button'; import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'; import {Skeleton} from '@/components/ui/skeleton'; import {Switch} from '@/components/ui/switch'; import FormField from '../components/FormField.jsx'; import {settingsSchema} from '../utils/validation.js'; import {usePreferences} from '../context/PreferencesContext.jsx'; const BACKEND_ERROR_KEY = { STEAM_ID_ALREADY_IN_USE: 'REGISTRATION.ERROR_STEAM_ID_IN_USE', }; const SUPPORTED_LANGUAGES = ['en', 'de']; const formatDate = (value, locale) => { if (!value) return '—'; try { return new Date(value).toLocaleString(locale); } catch { return value; } }; export default function SettingsPage() { const {t, i18n} = useTranslation(); const navigate = useNavigate(); const {isDarkMode, setDarkMode, setLanguage} = usePreferences(); const [profile, setProfile] = useState(null); const [isLoading, setIsLoading] = useState(true); const [loadError, setLoadError] = useState(null); const { register, handleSubmit, reset, formState: {errors, isSubmitting}, } = useForm({ resolver: zodResolver(settingsSchema), defaultValues: {useDarkTheme: true, language: 'en', steamId: ''}, }); useEffect(() => { const token = localStorage.getItem('tbp_jwt'); if (!token) { navigate('/login'); return; } const load = async () => { try { const response = await fetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (response.status === 401) { localStorage.removeItem('tbp_jwt'); navigate('/login'); return; } if (!response.ok) { setLoadError(t('INFO.ERROR.LOAD_PROFILE')); return; } const data = await response.json(); setProfile(data); reset({ useDarkTheme: !!data.settings?.useDarkTheme, language: data.settings?.language || 'en', steamId: data.steamId ?? '', }); } catch (err) { console.error('Settings load error:', err); setLoadError(t('INFO.ERROR.NETWORK_FAILURE')); } finally { setIsLoading(false); } }; load(); }, [navigate, reset, t]); const persistSettings = async (payload) => { const token = localStorage.getItem('tbp_jwt'); if (!token) { navigate('/login'); return {ok: false}; } try { const response = await fetch(`${import.meta.env.VITE_API_URL}/api/users/settings`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (response.status === 401) { localStorage.removeItem('tbp_jwt'); navigate('/login'); return {ok: false}; } if (!response.ok) { const data = await response.json().catch(() => ({})); const mappedKey = BACKEND_ERROR_KEY[data.code]; toast.error(mappedKey ? t(mappedKey) : data.message || t('SETTINGS.ERROR_SAVE')); return {ok: false}; } const data = await response.json(); localStorage.setItem('tbp_jwt', data.token); // Core reads the steamId from the token toast.success(t('SETTINGS.SAVED')); return {ok: true}; } catch (err) { console.error('Settings save error:', err); toast.error(t('INFO.ERROR.NETWORK_FAILURE')); return {ok: false}; } }; const handleDarkThemeToggle = async () => { const next = !isDarkMode; setDarkMode(next); // apply on the fly await persistSettings({ useDarkTheme: next, language: i18n.language, steamId: profile?.steamId ?? null, }); }; const handleLanguageChange = async (next) => { setLanguage(next); // apply on the fly await persistSettings({ useDarkTheme: isDarkMode, language: next, steamId: profile?.steamId ?? null, }); }; const onSubmitSteamId = async (values) => { const result = await persistSettings({ useDarkTheme: isDarkMode, language: i18n.language, steamId: values.steamId, }); if (result.ok) { setProfile((p) => (p ? {...p, steamId: values.steamId} : p)); } }; if (isLoading) { return (