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 {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 inputClass = 'w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:outline-none'; const inputErrorClass = 'w-full px-4 py-2 border border-red-500 dark:border-red-500 rounded-lg bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:outline-none'; const readOnlyClass = 'w-full px-4 py-2 border border-slate-200 dark:border-slate-700 rounded-lg bg-slate-100 dark:bg-slate-900 text-slate-500 dark:text-slate-400 cursor-not-allowed'; 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 [feedback, setFeedback] = useState({message: '', isError: false}); 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]; setFeedback({ message: mappedKey ? t(mappedKey) : data.message || t('SETTINGS.ERROR_SAVE'), isError: true, }); return {ok: false}; } const data = await response.json(); localStorage.setItem('tbp_jwt', data.token); // Core reads the steamId from the token setFeedback({message: t('SETTINGS.SAVED'), isError: false}); return {ok: true}; } catch (err) { console.error('Settings save error:', err); setFeedback({message: t('INFO.ERROR.NETWORK_FAILURE'), isError: true}); 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 (event) => { const next = event.target.value; 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)); } }; const renderError = (field) => errors[field] && (
{t(errors[field].message)}
); if (isLoading) { return (