import { useEffect, useRef, useState } from 'react' import { AuthError } from '@/shared/api' import { t } from '@/shared/i18n' export type Notice = { kind: 'error' | 'info' | 'success'; text: string } export type Errors = Record const fieldCodes: Record = { invalid_email: 'email', email_taken: 'email', invalid_username: 'username', username_taken: 'username', invalid_password: 'password', invalid_birth_date: 'birth', under_minimum_age: 'birth', invalid_email_code: 'code', email_code_attempts_exceeded: 'code', totp_code_invalid: 'code', totp_step_reused: 'code', recovery_code_invalid: 'code', } export function useTask() { const [busy, setBusy] = useState(false) const [notice, setNotice] = useState() const [errors, setErrors] = useState({}) const [code, setCode] = useState('') const [retryUntil, setRetryUntil] = useState(0) const active = useRef(null) useEffect(() => () => active.current?.abort(), []) function fail(next: Errors) { setErrors(next) setCode('') setNotice({ kind: 'error', text: Object.values(next)[0] }) requestAnimationFrame(() => document.querySelector('[aria-invalid="true"]')?.focus()) return false } async function run(work: (signal: AbortSignal) => Promise) { if (active.current || Date.now() < retryUntil) return const controller = new AbortController() active.current = controller setBusy(true) setNotice(undefined) setErrors({}) setCode('') try { await work(controller.signal) } catch (error) { if (controller.signal.aborted) return const text = error instanceof Error ? error.message : t('error.unexpected') setNotice({ kind: 'error', text }) if (error instanceof AuthError) { setCode(error.code) if (error.retryAfter) setRetryUntil(Date.now() + error.retryAfter * 1000) const field = fieldCodes[error.code] if (field) { setErrors({ [field]: text }) requestAnimationFrame(() => document.querySelector('[aria-invalid="true"]')?.focus()) } } } finally { if (active.current === controller) { active.current = null if (!controller.signal.aborted) setBusy(false) } } } return { busy, notice, setNotice, errors, code, fail, run, retryUntil } }