SkateSpots/src/components/ErrorFallback.tsx
SkateSpots caa5f8c56e fix: tier 4 — offer role trust, proof replay, crew alerts, error/nav i18n
- H13 offer threads: the seller is ALWAYS the listing author (derived from the
  event pubkey), never a self-declared role tag — a non-owner can no longer
  appear as the seller with a payment pointer.
- H12 trade reputation: a preimage/deposit proof counts at most once, so a real
  payment can't be reused across attestations to inflate payment-verified.
- Crew alerts: listed crews are filtered against the user's own membership
  confirmation labels (one batched query), so a confirmed member no longer gets
  a phantom 'confirm to join' badge/notification when the crew is edited.
- Error boundaries (app + route) and DesktopNavManager are translated; the
  class boundary renders a translated functional ErrorFallback. New batch4
  namespaces errorsUi.* / desktopNav.*.
2026-10-02 23:22:13 +07:00

79 lines
2.8 KiB
TypeScript

import { useTranslation } from '@/contexts/language';
/**
* Translated error-boundary UI. A FUNCTION component so it can use `t()` — the
* class ErrorBoundary renders it, which keeps the class (React error boundaries
* must be classes) while making the copy language-aware.
*/
export function ErrorFallback({
variant,
onReset,
onReload,
onBack,
}: {
variant: 'app' | 'route';
onReset?: () => void;
onReload: () => void;
onBack?: () => void;
}) {
const { t } = useTranslation();
const isApp = variant === 'app';
return (
<div
className={
isApp
? 'min-h-screen bg-black text-white flex items-center justify-center p-4'
: 'min-h-[60vh] flex items-center justify-center p-6'
}
>
<div className={isApp ? 'max-w-md w-full space-y-6 text-center' : 'max-w-sm w-full text-center space-y-5'}>
<div
className={
isApp
? 'mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-[#ff0040]/15 border border-[#ff0040]/30 text-4xl'
: 'mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-[#ff0040]/15 border border-[#ff0040]/30 text-3xl'
}
>
🛹
</div>
<div className={isApp ? 'space-y-2' : 'space-y-1.5'}>
<h2 className={isApp ? 'text-2xl font-black uppercase text-[#ff0040]' : 'text-xl font-black uppercase text-[#ff0040]'}>
{isApp ? t('errorsUi.slammedTitle') : t('errorsUi.pageSlammedTitle')}
</h2>
<p className={isApp ? 'text-gray-400' : 'text-sm text-gray-400'}>
{isApp ? t('errorsUi.slammedBody') : t('errorsUi.pageSlammedBody')}
</p>
</div>
<div className="flex flex-col sm:flex-row gap-2">
{onReset && (
<button
onClick={onReset}
className="flex-1 px-4 py-3 rounded-full font-black uppercase bg-[#ff0040] text-white hover:bg-[#ff0040]/90 transition-colors"
>
{t('errorsUi.tryAgain')}
</button>
)}
<button
onClick={onReload}
className={
isApp
? 'flex-1 px-4 py-3 rounded-full font-bold border border-white/20 text-white hover:bg-white/5 transition-colors'
: 'flex-1 px-4 py-3 rounded-full font-black uppercase bg-[#ff0040] text-white hover:bg-[#ff0040]/90 transition-colors'
}
>
{isApp ? t('errorsUi.reloadApp') : t('errorsUi.reload')}
</button>
{onBack && (
<button
onClick={onBack}
className="flex-1 px-4 py-3 rounded-full font-black uppercase bg-white/5 text-gray-300 border border-white/10 hover:bg-white/10 transition-colors"
>
{t('errorsUi.goBack')}
</button>
)}
</div>
</div>
</div>
);
}