Files
explore/ui/shared/AppError/AppError.tsx
T
zeekay 5bbb57995a top accounts: read the tags the list actually carries
The addresses list endpoint returns a projection of an address — no tags, no
name, no implementations. AddressesListItem guarded with `public_tags !== null`,
which is true of an absent field, so every visit to /accounts read `.length` off
undefined and the whole page fell to the error boundary. The type said the field
was always present, so nothing caught it.

Partial<AddressParam> states what the endpoint sends, and both read sites reach
for the tags the same optional way.

The error screen it fell to had its own defect: the status glyph is an
inline-block div and gui's H1 renders inline, so the two shared a line box and
"500" ran into "Oops! Something went wrong". The screen is a stack; it now says
so. The heading's own top margin was being outranked by gui's atomic reset.
2026-08-28 23:45:54 -07:00

125 lines
4.6 KiB
TypeScript

import React from 'react';
import { route } from 'nextjs-routes';
import getErrorCause from 'lib/errors/getErrorCause';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode';
import getErrorProp from 'lib/errors/getErrorProp';
import getResourceErrorPayload from 'lib/errors/getResourceErrorPayload';
import { Button } from '@luxfi/ui/button';
import { Link } from 'toolkit/next/link';
import AdBanner from '../ad/AdBanner';
import AppErrorIcon from './AppErrorIcon';
import AppErrorTitle from './AppErrorTitle';
import AppErrorBlockConsensus from './custom/AppErrorBlockConsensus';
import AppErrorTooManyRequests from './custom/AppErrorTooManyRequests';
import AppErrorTxNotFound from './custom/AppErrorTxNotFound';
interface Props {
className?: string;
error: Error | undefined;
}
const ERROR_TEXTS: Record<string, { title: string; text: string }> = {
'403': {
title: 'Alert',
text: 'Access to this resource is restricted.',
},
'404': {
title: 'Page not found',
text: 'This page is no longer explorable! If you are lost, use the search bar to find what you are looking for.',
},
'422': {
title: 'Request cannot be processed',
text: 'Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info.',
},
'500': {
title: 'Oops! Something went wrong',
text: 'An unexpected error has occurred. Try reloading the page, or come back soon and try again.',
},
};
const AppError = ({ error, className }: Props) => {
const content = (() => {
const resourceErrorPayload = getResourceErrorPayload(error);
const cause = getErrorCause(error);
const messageInPayload =
resourceErrorPayload &&
typeof resourceErrorPayload === 'object' &&
'message' in resourceErrorPayload &&
typeof resourceErrorPayload.message === 'string' ?
resourceErrorPayload.message :
undefined;
const statusCode = getErrorCauseStatusCode(error) || getErrorObjStatusCode(error);
const isInvalidTxHash = cause && 'resource' in cause && cause.resource === 'general:tx' && statusCode === 404;
const isBlockConsensus = messageInPayload?.includes('Block lost consensus');
if (isInvalidTxHash) {
return <AppErrorTxNotFound/>;
}
if (isBlockConsensus) {
const hash =
resourceErrorPayload &&
typeof resourceErrorPayload === 'object' &&
'hash' in resourceErrorPayload &&
typeof resourceErrorPayload.hash === 'string' ?
resourceErrorPayload.hash :
undefined;
return <AppErrorBlockConsensus hash={ hash }/>;
}
switch (statusCode) {
case 429: {
const rateLimits = getErrorProp(error, 'rateLimits');
const bypassOptions = typeof rateLimits === 'object' && rateLimits && 'bypassOptions' in rateLimits ? rateLimits.bypassOptions : undefined;
const reset = typeof rateLimits === 'object' && rateLimits && 'reset' in rateLimits ? rateLimits.reset : undefined;
return (
<AppErrorTooManyRequests
bypassOptions={ typeof bypassOptions === 'string' ? bypassOptions : undefined }
reset={ typeof reset === 'string' ? reset : undefined }/>
);
}
default: {
const { title, text } = ERROR_TEXTS[String(statusCode)] ?? ERROR_TEXTS[500];
return (
<>
<AppErrorIcon statusCode={ statusCode }/>
<AppErrorTitle title={ title }/>
<p className="text-[var(--color-text-secondary)] mt-3">{ text }</p>
<Link
href={ route({ pathname: '/' }) }
>
<Button
className="mt-8"
variant="outline"
>
Back to home
</Button>
</Link>
{ statusCode === 404 && <AdBanner className="mt-12"/> }
</>
);
}
}
})();
return (
// The pieces below — status glyph, heading, copy, button — are written as a
// plain sequence and read as a stack. Nothing here can rely on them being
// block-level: the glyph is an inline-block div and gui's H1 renders inline,
// so in normal flow they share a line box and the glyph runs straight into
// the headline. A column lays them out as the markup reads.
<div className={ `mt-[52px] lg:mt-[104px] max-w-[800px] flex flex-col items-start ${ className || '' }` }>
{ content }
</div>
);
};
export default React.memo(AppError);