Public Access
feat(registration): show the wait time after too many attempts
This commit is contained in:
1 parent
2cacc88844
commit
8dcd03f219
4 files changed
+63
-3
No files matched your search
+2
-1
@@ -32,7 +32,8 @@
|
||||
"ERROR_INVALID_INVITE_CODE": "Der Einladungscode ist ungültig oder wurde bereits verwendet.",
|
||||
"ERROR_STEAM_ID_IN_USE": "Diese Steam-ID ist bereits mit einem anderen Konto verknüpft.",
|
||||
"DISPLAY_NAME": "Anzeigename (optional)",
|
||||
"ERROR_DISPLAY_NAME_IN_USE": "Dieser Anzeigename ist schon vergeben."
|
||||
"ERROR_DISPLAY_NAME_IN_USE": "Dieser Anzeigename ist schon vergeben.",
|
||||
"ERROR_TOO_MANY_ATTEMPTS": "Zu viele fehlgeschlagene Versuche. Bitte in {{minutes}} Minuten erneut versuchen."
|
||||
},
|
||||
"SETTINGS": {
|
||||
"TITLE": "Einstellungen",
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
"ERROR_INVALID_INVITE_CODE": "The invite code is invalid or has already been used.",
|
||||
"ERROR_STEAM_ID_IN_USE": "This Steam ID is already linked to another account.",
|
||||
"DISPLAY_NAME": "Display name (optional)",
|
||||
"ERROR_DISPLAY_NAME_IN_USE": "This display name is already taken."
|
||||
"ERROR_DISPLAY_NAME_IN_USE": "This display name is already taken.",
|
||||
"ERROR_TOO_MANY_ATTEMPTS": "Too many failed attempts. Please try again in {{minutes}} minutes."
|
||||
},
|
||||
"SETTINGS": {
|
||||
"TITLE": "Settings",
|
||||
|
||||
@@ -18,6 +18,7 @@ const BACKEND_ERROR_KEY = {
|
||||
INVALID_INVITATION_CODE: 'REGISTRATION.ERROR_INVALID_INVITE_CODE',
|
||||
STEAM_ID_ALREADY_IN_USE: 'REGISTRATION.ERROR_STEAM_ID_IN_USE',
|
||||
DISPLAY_NAME_ALREADY_IN_USE: 'REGISTRATION.ERROR_DISPLAY_NAME_IN_USE',
|
||||
TOO_MANY_ATTEMPTS: 'REGISTRATION.ERROR_TOO_MANY_ATTEMPTS',
|
||||
};
|
||||
|
||||
export default function RegistrationPage() {
|
||||
@@ -65,8 +66,11 @@ export default function RegistrationPage() {
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
const mappedKey = BACKEND_ERROR_KEY[data.code];
|
||||
const options = response.status === 429
|
||||
? {minutes: Math.ceil(Number(response.headers.get('Retry-After') ?? 900) / 60)}
|
||||
: undefined;
|
||||
setFeedback({
|
||||
message: mappedKey ? t(mappedKey) : data.message || t('REGISTRATION.ERROR_GENERIC'),
|
||||
message: mappedKey ? t(mappedKey, options) : data.message || t('REGISTRATION.ERROR_GENERIC'),
|
||||
isError: true,
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||
import {cleanup, fireEvent, render, screen} from '@testing-library/react';
|
||||
import {MemoryRouter} from 'react-router-dom';
|
||||
import RegistrationPage from './RegistrationPage.jsx';
|
||||
|
||||
// Own mock: the shared one drops the {{minutes}} value this test checks.
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key, options) => (options?.minutes === undefined ? key : `${key}:${options.minutes}`),
|
||||
i18n: {language: 'de'},
|
||||
}),
|
||||
}));
|
||||
|
||||
function errorResponse(status, code, headers = {}) {
|
||||
return {
|
||||
ok: false,
|
||||
status,
|
||||
headers: new Headers(headers),
|
||||
json: async () => ({code}),
|
||||
};
|
||||
}
|
||||
|
||||
function submitValidForm() {
|
||||
render(<MemoryRouter><RegistrationPage/></MemoryRouter>);
|
||||
fireEvent.change(screen.getByLabelText('REGISTRATION.EMAIL'), {target: {value: 'a@b.de'}});
|
||||
fireEvent.change(screen.getByLabelText('REGISTRATION.PASSWORD'), {target: {value: 'secret123'}});
|
||||
fireEvent.change(screen.getByLabelText('REGISTRATION.PASSWORD_CONFIRMATION'), {target: {value: 'secret123'}});
|
||||
fireEvent.change(screen.getByLabelText('REGISTRATION.INVITE_CODE'), {target: {value: 'code'}});
|
||||
fireEvent.change(screen.getByLabelText('REGISTRATION.STEAM_ID'), {target: {value: '76561198000000001'}});
|
||||
fireEvent.click(screen.getByRole('button', {name: 'REGISTRATION.SUBMIT'}));
|
||||
}
|
||||
|
||||
describe('RegistrationPage', () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('shows the wait time when the IP is blocked', async () => {
|
||||
globalThis.fetch = vi.fn(async () => errorResponse(429, 'TOO_MANY_ATTEMPTS', {'Retry-After': '900'}));
|
||||
|
||||
submitValidForm();
|
||||
|
||||
expect(await screen.findByText('REGISTRATION.ERROR_TOO_MANY_ATTEMPTS:15')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('maps a known backend error to its message', async () => {
|
||||
globalThis.fetch = vi.fn(async () => errorResponse(400, 'INVALID_INVITATION_CODE'));
|
||||
|
||||
submitValidForm();
|
||||
|
||||
expect(await screen.findByText('REGISTRATION.ERROR_INVALID_INVITE_CODE')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user