import { useEffect, useState } from 'react'; import { useTranslation } from '../contexts/LanguageContext'; import { useSidebar } from '../contexts/SidebarContext'; import { sessionsAPI, twoFactorAPI } from '../api/client'; import type { UserSession } from '../api/client'; import '../styles/SettingsPage.css'; export default function Settings() { const { t, language, setLanguage } = useTranslation(); const { toggleMobileMenu } = useSidebar(); const [twoFactorStatus, setTwoFactorStatus] = useState<{ enabled: boolean; has_backup_codes: boolean } | null>(null); const [twoFactorLoading, setTwoFactorLoading] = useState(true); const [twoFactorBusy, setTwoFactorBusy] = useState(false); const [twoFactorError, setTwoFactorError] = useState(''); const [setupData, setSetupData] = useState<{ secret: string; uri: string; qr_code: string } | null>(null); const [verifyCode, setVerifyCode] = useState(''); const [backupCodes, setBackupCodes] = useState(null); const [regenerateCode, setRegenerateCode] = useState(''); const [disablePassword, setDisablePassword] = useState(''); const [disableCode, setDisableCode] = useState(''); const [sessions, setSessions] = useState([]); const [sessionsLoading, setSessionsLoading] = useState(true); const [sessionsBusy, setSessionsBusy] = useState(false); const [sessionsError, setSessionsError] = useState(''); const loadTwoFactorStatus = async () => { try { const status = await twoFactorAPI.getStatus(); setTwoFactorStatus(status); } catch (err: any) { setTwoFactorError(err?.response?.data?.detail || t.common.error); } finally { setTwoFactorLoading(false); } }; useEffect(() => { loadTwoFactorStatus(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const loadSessions = async () => { setSessionsError(''); setSessionsLoading(true); try { const data = await sessionsAPI.list(); setSessions(data.items || []); } catch (err: any) { setSessionsError(err?.response?.data?.detail || t.common.error); } finally { setSessionsLoading(false); } }; useEffect(() => { loadSessions(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const revokeSession = async (sessionId: string) => { setSessionsError(''); setSessionsBusy(true); try { await sessionsAPI.revoke(sessionId); await loadSessions(); } catch (err: any) { setSessionsError(err?.response?.data?.detail || t.common.error); } finally { setSessionsBusy(false); } }; const revokeAllOtherSessions = async () => { setSessionsError(''); setSessionsBusy(true); try { await sessionsAPI.revokeAllOther(); await loadSessions(); } catch (err: any) { setSessionsError(err?.response?.data?.detail || t.common.error); } finally { setSessionsBusy(false); } }; const startTwoFactorSetup = async () => { setTwoFactorError(''); setBackupCodes(null); setTwoFactorBusy(true); try { const data = await twoFactorAPI.setup(); setSetupData(data); } catch (err: any) { setTwoFactorError(err?.response?.data?.detail || t.common.error); } finally { setTwoFactorBusy(false); } }; const verifyAndEnableTwoFactor = async () => { setTwoFactorError(''); setTwoFactorBusy(true); try { const result = await twoFactorAPI.verify(verifyCode); setBackupCodes(result.backup_codes || []); setSetupData(null); setVerifyCode(''); await loadTwoFactorStatus(); } catch (err: any) { setTwoFactorError(err?.response?.data?.detail || t.common.error); } finally { setTwoFactorBusy(false); } }; const regenerateTwoFactorBackupCodes = async () => { setTwoFactorError(''); setTwoFactorBusy(true); try { const result = await twoFactorAPI.regenerateBackupCodes(regenerateCode); setBackupCodes(result.backup_codes || []); setRegenerateCode(''); await loadTwoFactorStatus(); } catch (err: any) { setTwoFactorError(err?.response?.data?.detail || t.common.error); } finally { setTwoFactorBusy(false); } }; const disableTwoFactor = async () => { setTwoFactorError(''); setTwoFactorBusy(true); try { await twoFactorAPI.disable({ password: disablePassword, code: disableCode }); setDisablePassword(''); setDisableCode(''); setBackupCodes(null); setSetupData(null); await loadTwoFactorStatus(); } catch (err: any) { setTwoFactorError(err?.response?.data?.detail || t.common.error); } finally { setTwoFactorBusy(false); } }; return (
{t.settings.title}

{t.settings.preferences}

language

{t.settings.language}

{t.settings.languageDesc}

{t.settings.security}

shield

{t.settings.twoFactorTitle}

{t.settings.twoFactorDesc}

{twoFactorLoading ? ( {t.common.loading} ) : ( {twoFactorStatus?.enabled ? t.settings.enabled : t.settings.disabled} )}
{twoFactorError &&
{twoFactorError}
} {!twoFactorLoading && twoFactorStatus && (
{!twoFactorStatus.enabled && ( <> {!setupData ? ( ) : (
{t.settings.qrCodeAlt}
{t.settings.secret}
{setupData.secret}
setVerifyCode(e.target.value)} minLength={6} maxLength={8} autoComplete="one-time-code" />
)} )} {twoFactorStatus.enabled && (

{t.settings.backupCodes}

{t.settings.backupCodesDesc}

setRegenerateCode(e.target.value)} minLength={6} maxLength={8} autoComplete="one-time-code" />

{t.settings.disable2fa}

{t.settings.disable2faDesc}

setDisablePassword(e.target.value)} autoComplete="current-password" />
setDisableCode(e.target.value)} minLength={6} maxLength={8} autoComplete="one-time-code" />
)} {backupCodes && backupCodes.length > 0 && (

{t.settings.backupCodes}

{t.settings.backupCodesSaveHint}

{backupCodes.map((code) => ( {code} ))}
)}
)}

{t.settings.sessionsTitle}

{t.settings.sessionsDesc}

{sessionsError &&
{sessionsError}
}
{sessionsLoading ? (
{t.common.loading}
) : sessions.length === 0 ? (
{t.settings.sessionsEmpty}
) : (
{sessions.map((s) => (
{s.device_name || t.settings.unknownDevice} {s.is_current && {t.settings.currentSession}} {!s.is_active && {t.settings.inactiveSession}}
{s.browser || t.settings.unknownBrowser} • {s.os || t.settings.unknownOs} {s.ip_address && • {s.ip_address}}
{t.settings.lastActive}: {new Date(s.last_active_at).toLocaleString()}
{!s.is_current && s.is_active && ( )}
))}
)}
); }