feat(registration): show the wait time after too many attempts

This commit is contained in:
lobo committed 2026-09-25 09:06:30 +02:00
1 parent 2cacc88844
commit 8dcd03f219
4 files changed
+63 -3

No files matched your search

+2 -1
View File
@@ -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
View File
@@ -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",
+5 -1
View File
@@ -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) {
+54
View File
@@ -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();
});
});