Skip to content

Commit 94bb18e

Browse files
committed
Update 429 page wording (#2959)
Fixes #2956
1 parent 98643f9 commit 94bb18e

File tree

6 files changed

+61
-5
lines changed

6 files changed

+61
-5
lines changed

lib/hooks/useFetch.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default function useFetch() {
5656
statusText: response.statusText,
5757
rateLimits: {
5858
bypassOptions: response.headers.get('bypass-429-option'),
59+
reset: response.headers.get('x-ratelimit-reset'),
5960
},
6061
};
6162

ui/shared/AppError/AppError.pw.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ test('too many requests +@mobile', async({ render }) => {
5151
const error = {
5252
message: 'Too many requests',
5353
cause: { status: 429 },
54-
} as Error;
54+
rateLimits: { bypassOptions: 'temporary_token', reset: '42000' },
55+
} as unknown as Error;
5556
const component = await render(<AppError error={ error }/>);
5657
await expect(component).toHaveScreenshot();
5758
});

ui/shared/AppError/AppError.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ const AppError = ({ error, className }: Props) => {
8080
case 429: {
8181
const rateLimits = getErrorProp(error, 'rateLimits');
8282
const bypassOptions = typeof rateLimits === 'object' && rateLimits && 'bypassOptions' in rateLimits ? rateLimits.bypassOptions : undefined;
83-
return <AppErrorTooManyRequests bypassOptions={ typeof bypassOptions === 'string' ? bypassOptions : undefined }/>;
83+
const reset = typeof rateLimits === 'object' && rateLimits && 'reset' in rateLimits ? rateLimits.reset : undefined;
84+
return (
85+
<AppErrorTooManyRequests
86+
bypassOptions={ typeof bypassOptions === 'string' ? bypassOptions : undefined }
87+
reset={ typeof reset === 'string' ? reset : undefined }/>
88+
);
8489
}
8590

8691
default: {
-364 Bytes
Loading
-224 Bytes
Loading

ui/shared/AppError/custom/AppErrorTooManyRequests.tsx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,31 @@ import buildUrl from 'lib/api/buildUrl';
66
import useFetch from 'lib/hooks/useFetch';
77
import { Button } from 'toolkit/chakra/button';
88
import { toaster } from 'toolkit/chakra/toaster';
9+
import { SECOND } from 'toolkit/utils/consts';
10+
import { apos } from 'toolkit/utils/htmlEntities';
911
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
1012
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
1113

1214
import AppErrorIcon from '../AppErrorIcon';
1315
import AppErrorTitle from '../AppErrorTitle';
1416

17+
function formatTimeLeft(timeLeft: number) {
18+
const hours = Math.floor(timeLeft / 3600);
19+
const minutes = Math.floor((timeLeft % 3600) / 60);
20+
const seconds = timeLeft % 60;
21+
22+
return `${ hours.toString().padStart(2, '0') }h ${ minutes.toString().padStart(2, '0') }m ${ seconds.toString().padStart(2, '0') }s`;
23+
}
24+
1525
interface Props {
1626
bypassOptions?: string;
27+
reset?: string;
1728
}
1829

19-
const AppErrorTooManyRequests = ({ bypassOptions }: Props) => {
30+
const AppErrorTooManyRequests = ({ bypassOptions, reset }: Props) => {
31+
32+
const [ timeLeft, setTimeLeft ] = React.useState(reset ? Math.ceil(Number(reset) / SECOND) : undefined);
33+
2034
const fetch = useFetch();
2135
const recaptcha = useReCaptcha();
2236

@@ -52,19 +66,54 @@ const AppErrorTooManyRequests = ({ bypassOptions }: Props) => {
5266
}
5367
}, [ recaptcha, fetch ]);
5468

69+
React.useEffect(() => {
70+
if (reset === undefined) {
71+
return;
72+
}
73+
74+
const interval = window.setInterval(() => {
75+
setTimeLeft((prev) => {
76+
if (prev && prev > 1) {
77+
return prev - 1;
78+
}
79+
80+
window.clearInterval(interval);
81+
window.location.reload();
82+
83+
return 0;
84+
});
85+
}, SECOND);
86+
87+
return () => {
88+
window.clearInterval(interval);
89+
};
90+
}, [ reset ]);
91+
5592
if (!config.services.reCaptchaV2.siteKey) {
5693
throw new Error('reCAPTCHA V2 site key is not set');
5794
}
5895

96+
const text = (() => {
97+
if (timeLeft === undefined && bypassOptions === 'no_bypass') {
98+
return 'Rate limit exceeded.';
99+
}
100+
101+
const timeLeftText = timeLeft !== undefined ? `wait ${ formatTimeLeft(timeLeft) } ` : '';
102+
const bypassText = bypassOptions !== 'no_bypass' ? `verify you${ apos }re human ` : '';
103+
const orText = timeLeft !== undefined && bypassOptions !== 'no_bypass' ? 'OR ' : '';
104+
105+
return `Rate limit exceeded. Please ${ timeLeftText }${ orText }${ bypassText }before making another request.`;
106+
})();
107+
59108
return (
60109
<>
61110
<AppErrorIcon statusCode={ 429 }/>
62111
<AppErrorTitle title="Too many requests"/>
63112
<Text color="text.secondary" mt={ 3 }>
64-
You have exceeded the request rate for a given time period. Please reduce the number of requests and try again soon.
113+
{ text }
65114
</Text>
66115
<ReCaptcha { ...recaptcha }/>
67-
{ bypassOptions !== 'no_bypass' && <Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>Try again</Button> }
116+
{ bypassOptions !== 'no_bypass' && <Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>I'm not a robot</Button> }
68117
</>
69118
);
70119
};

0 commit comments

Comments
 (0)