mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
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.
125 lines
4.6 KiB
TypeScript
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);
|