Update 429 page wording (#2959)

Fixes #2956
This commit is contained in:
tom goriunov
2025-08-20 19:50:54 +02:00
committed by GitHub
parent 7e9ee6623b
commit 8638b62d6c
6 changed files with 63 additions and 7 deletions
+1
View File
@@ -56,6 +56,7 @@ export default function useFetch() {
statusText: response.statusText,
rateLimits: {
bypassOptions: response.headers.get('bypass-429-option'),
reset: response.headers.get('x-ratelimit-reset'),
},
};
+2 -1
View File
@@ -51,7 +51,8 @@ test('too many requests +@mobile', async({ render }) => {
const error = {
message: 'Too many requests',
cause: { status: 429 },
} as Error;
rateLimits: { bypassOptions: 'temporary_token', reset: '42000' },
} as unknown as Error;
const component = await render(<AppError error={ error }/>);
await expect(component).toHaveScreenshot();
});
+6 -1
View File
@@ -80,7 +80,12 @@ const AppError = ({ error, className }: Props) => {
case 429: {
const rateLimits = getErrorProp(error, 'rateLimits');
const bypassOptions = typeof rateLimits === 'object' && rateLimits && 'bypassOptions' in rateLimits ? rateLimits.bypassOptions : undefined;
return <AppErrorTooManyRequests bypassOptions={ typeof bypassOptions === 'string' ? 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: {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

@@ -6,17 +6,31 @@ import buildUrl from 'lib/api/buildUrl';
import useFetch from 'lib/hooks/useFetch';
import { Button } from 'toolkit/chakra/button';
import { toaster } from 'toolkit/chakra/toaster';
import { SECOND } from 'toolkit/utils/consts';
import { apos } from 'toolkit/utils/htmlEntities';
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
import AppErrorIcon from '../AppErrorIcon';
import AppErrorTitle from '../AppErrorTitle';
interface Props {
bypassOptions?: string;
function formatTimeLeft(timeLeft: number) {
const hours = Math.floor(timeLeft / 3600);
const minutes = Math.floor((timeLeft % 3600) / 60);
const seconds = timeLeft % 60;
return `${ hours.toString().padStart(2, '0') }h ${ minutes.toString().padStart(2, '0') }m ${ seconds.toString().padStart(2, '0') }s`;
}
const AppErrorTooManyRequests = ({ bypassOptions }: Props) => {
interface Props {
bypassOptions?: string;
reset?: string;
}
const AppErrorTooManyRequests = ({ bypassOptions, reset }: Props) => {
const [ timeLeft, setTimeLeft ] = React.useState(reset ? Math.ceil(Number(reset) / SECOND) : undefined);
const fetch = useFetch();
const recaptcha = useReCaptcha();
@@ -52,19 +66,54 @@ const AppErrorTooManyRequests = ({ bypassOptions }: Props) => {
}
}, [ recaptcha, fetch ]);
React.useEffect(() => {
if (reset === undefined) {
return;
}
const interval = window.setInterval(() => {
setTimeLeft((prev) => {
if (prev && prev > 1) {
return prev - 1;
}
window.clearInterval(interval);
window.location.reload();
return 0;
});
}, SECOND);
return () => {
window.clearInterval(interval);
};
}, [ reset ]);
if (!config.services.reCaptchaV2.siteKey) {
throw new Error('reCAPTCHA V2 site key is not set');
}
const text = (() => {
if (timeLeft === undefined && bypassOptions === 'no_bypass') {
return 'Rate limit exceeded.';
}
const timeLeftText = timeLeft !== undefined ? `wait ${ formatTimeLeft(timeLeft) } ` : '';
const bypassText = bypassOptions !== 'no_bypass' ? `verify you${ apos }re human ` : '';
const orText = timeLeft !== undefined && bypassOptions !== 'no_bypass' ? 'OR ' : '';
return `Rate limit exceeded. Please ${ timeLeftText }${ orText }${ bypassText }before making another request.`;
})();
return (
<>
<AppErrorIcon statusCode={ 429 }/>
<AppErrorTitle title="Too many requests"/>
<Text color="text.secondary" mt={ 3 }>
You have exceeded the request rate for a given time period. Please reduce the number of requests and try again soon.
{ text }
</Text>
<ReCaptcha { ...recaptcha }/>
{ bypassOptions !== 'no_bypass' && <Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>Try again</Button> }
{ bypassOptions !== 'no_bypass' && <Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>I'm not a robot</Button> }
</>
);
};