import { useCallback, useEffect, useState } from 'react' import { api } from '../../api/client.js' import RecoveryCodesDisplay from './RecoveryCodesDisplay.jsx' // Self-service recovery (backup) codes. Shows how many remain and lets the user // regenerate a fresh set (password step-up). Shown only when 2FA is enabled. // `hasPassword` decides whether the current-password field is required — an // SSO-only account with no password may regenerate while authenticated. export default function RecoveryCodesPanel({ hasPassword = true }) { const [remaining, setRemaining] = useState(null) const [currentPassword, setCurrentPassword] = useState('') const [codes, setCodes] = useState(null) // freshly generated batch, shown once const [busy, setBusy] = useState(false) const [error, setError] = useState('') const load = useCallback(async () => { try { const { remaining: n } = await api.recoveryCodesStatus() setRemaining(n) } catch { /* non-fatal — the panel still offers regeneration */ } }, []) useEffect(() => { load() }, [load]) async function regenerate() { setBusy(true) setError('') try { const { recoveryCodes } = await api.generateRecoveryCodes(hasPassword ? currentPassword : undefined) setCodes(recoveryCodes) setCurrentPassword('') await load() } catch (err) { setError(err.message || 'Could not generate recovery codes.') } finally { setBusy(false) } } return (
Single-use codes that let you sign in if you lose your authenticator. Regenerating replaces any codes you still have.
{remaining != null && !codes && (0 ? '#7fd0a4' : '#e0b352', fontSize: '0.86rem' }}> {remaining > 0 ? `${remaining} unused code${remaining === 1 ? '' : 's'} remaining.` : 'No unused recovery codes left — regenerate a set.'}
)} {codes ? ({error}
}