import type { ReactNode } from 'react' import { Loader2 } from 'lucide-react' import { API_PROBE_INTERVAL_MS, retryApiHealth, useApiHealth, useApiUnavailable } from '@/shared/api' import { useT } from '@/shared/i18n' import { Button } from '@/shared/ui' /** * App-wide「api error」intermediate state. * * `ApiErrorGate` sits above the router: while the rolling failure rate stays * above the threshold in `@/shared/api/health` it replaces the whole tree with * the outage screen, so a dead backend reads as one clear page instead of a * grid of half-rendered panels each showing its own error. * * Unmounting is also the recovery mechanism: the health store probes the * service on its own, and as soon as one request answers, the tree remounts and * every view refetches through `useAsync` — no document reload involved. */ export function ApiErrorGate({ children }: { children: ReactNode }) { const unavailable = useApiUnavailable() if (unavailable) return return <>{children} } function ServiceUnavailableScreen() { const { t } = useT() const { failures, samples, errorRate, burstFailures, lastFailureStatus, lastFailureUrl, probing, lastProbeAt, } = useApiHealth() const percent = Math.round(errorRate * 100) const path = lastFailureUrl ? lastFailureUrl.replace(/^https?:\/\/[^/]+/, '') : null /** Failure reason in words: status 0 / transport error means "no answer". */ const failureLabel = lastFailureStatus === null ? t('error.gate.noResponse') : lastFailureStatus === 0 ? t('error.gate.networkError') : lastFailureStatus >= 500 ? t('error.gate.serverError', { status: lastFailureStatus }) : `HTTP ${lastFailureStatus}` return (
{t('error.gate.imageAlt')}

{t('error.gate.title')}

{t('error.gate.body', { count: burstFailures })}

{t('error.gate.lastFailure', { reason: failureLabel, path: path ? ` · ${path}` : '', percent, failures, samples, })}

{t('error.gate.probeInterval', { seconds: Math.round(API_PROBE_INTERVAL_MS / 1000) })} {lastProbeAt ? ` · ${t('error.gate.lastProbe', { time: new Date(lastProbeAt).toLocaleTimeString([], { hour12: false }) })}` : ''}

) }