Public Access
refactor(limits): server limits in one file, own texts for unknown errors
This commit is contained in:
1 parent
f605917874
commit
24a98062ee
18 files changed
+84
-49
No files matched your search
@@ -15,8 +15,8 @@ import PageContainer from '../components/PageContainer.jsx';
|
||||
import useSessionExpired from '../hooks/useSessionExpired.js';
|
||||
import {hasSession} from '../utils/auth.js';
|
||||
import {createHint, endHint, fetchAdminHints} from '../utils/hints.js';
|
||||
import {MAX_HINT_LENGTH} from '../utils/limits.js';
|
||||
|
||||
const MAX_TEXT = 500;
|
||||
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// The value of a datetime-local input: local time, no seconds.
|
||||
@@ -129,11 +129,11 @@ export default function AdminHintsPage() {
|
||||
<form onSubmit={submit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="hintTextEn">{t('ADMIN_HINTS.TEXT_EN')}</Label>
|
||||
<Textarea id="hintTextEn" required maxLength={MAX_TEXT} value={form.textEn} onChange={change('textEn')}/>
|
||||
<Textarea id="hintTextEn" required maxLength={MAX_HINT_LENGTH} value={form.textEn} onChange={change('textEn')}/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="hintTextDe">{t('ADMIN_HINTS.TEXT_DE')}</Label>
|
||||
<Textarea id="hintTextDe" maxLength={MAX_TEXT} value={form.textDe} onChange={change('textDe')}/>
|
||||
<Textarea id="hintTextDe" maxLength={MAX_HINT_LENGTH} value={form.textDe} onChange={change('textDe')}/>
|
||||
<p className="text-xs text-muted-foreground">{t('ADMIN_HINTS.TEXT_HELP')}</p>
|
||||
</div>
|
||||
<fieldset className="space-y-2">
|
||||
|
||||
@@ -6,7 +6,8 @@ import SyncStatusBadge from '../components/SyncStatusBadge.jsx';
|
||||
import useForceSync from '../hooks/useForceSync.js';
|
||||
import useSessionExpired from '../hooks/useSessionExpired.js';
|
||||
import {formatPlaytime} from '../utils/games.js';
|
||||
import {GAME_STATUSES, libraryQuery, parseStatus} from '../utils/backlog.js';
|
||||
import {libraryQuery, parseStatus} from '../utils/backlog.js';
|
||||
import {GAME_STATUSES, MAX_LIBRARY_SEARCH_LENGTH} from '../utils/limits.js';
|
||||
import {ArrowUpDown, ChevronLeft, ChevronRight, Clock, EyeOff, Gamepad2, Library, Plus, RefreshCw, Search, SearchX, Timer, Trophy, Upload} from 'lucide-react';
|
||||
|
||||
import {Badge} from '@/components/ui/badge';
|
||||
@@ -269,7 +270,7 @@ export default function GameLibrary() {
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t('LIBRARY.SEARCH_PLACEHOLDER')}
|
||||
aria-label={t('LIBRARY.SEARCH_PLACEHOLDER')}
|
||||
maxLength={100}
|
||||
maxLength={MAX_LIBRARY_SEARCH_LENGTH}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -15,11 +15,11 @@ import PageContainer from '../components/PageContainer.jsx';
|
||||
import useSessionExpired from '../hooks/useSessionExpired.js';
|
||||
import useStartImport from '../hooks/useStartImport.js';
|
||||
import {hasSession} from '../utils/auth.js';
|
||||
import {GAME_STATUSES} from '../utils/backlog.js';
|
||||
import {
|
||||
checkUndo, getImport, listImports, MAX_IMPORT_ROWS, parseCsv, parseTextList, retryImport, saveImport, undoImport,
|
||||
checkUndo, getImport, listImports, parseCsv, parseTextList, retryImport, saveImport, undoImport,
|
||||
} from '../utils/imports.js';
|
||||
import {hoursToMinutes, MAX_PLATFORM_LENGTH, toHours} from '../utils/manualGames.js';
|
||||
import {GAME_STATUSES, MAX_IMPORT_ROWS, MAX_PLATFORM_LENGTH} from '../utils/limits.js';
|
||||
import {hoursToMinutes, toHours} from '../utils/manualGames.js';
|
||||
import {loadFavoritePlatform, preselectPlatform} from '../utils/platforms.js';
|
||||
|
||||
const POLL_MS = 2000;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {Skeleton} from '@/components/ui/skeleton';
|
||||
import {cn} from '@/lib/utils';
|
||||
import GameCover from '../components/GameCover.jsx';
|
||||
import GameStatusBadge, {STATUS_META} from '../components/GameStatusBadge.jsx';
|
||||
import {GAME_STATUSES} from '../utils/backlog.js';
|
||||
import {GAME_STATUSES} from '../utils/limits.js';
|
||||
import {formatPlaytime} from '../utils/games.js';
|
||||
import {hasSession} from '../utils/auth.js';
|
||||
import {normalizeProfileStyle} from '../utils/profileStyle.js';
|
||||
|
||||
@@ -13,6 +13,7 @@ import FeedbackAlert from '../components/FeedbackAlert.jsx';
|
||||
import FormField from '../components/FormField.jsx';
|
||||
import {registrationSchema} from '../utils/validation.js';
|
||||
import {clientFetch} from '../utils/api.js';
|
||||
import {MAX_DISPLAY_NAME_LENGTH} from '../utils/limits.js';
|
||||
|
||||
const BACKEND_ERROR_KEY = {
|
||||
EMAIL_ALREADY_IN_USE: 'REGISTRATION.ERROR_EMAIL_IN_USE',
|
||||
@@ -68,7 +69,7 @@ export default function RegistrationPage() {
|
||||
? {minutes: Math.ceil(Number(response.headers.get('Retry-After') ?? 900) / 60)}
|
||||
: undefined;
|
||||
setFeedback({
|
||||
message: mappedKey ? t(mappedKey, options) : data.message || t('REGISTRATION.ERROR_GENERIC'),
|
||||
message: t(mappedKey ?? 'REGISTRATION.ERROR_GENERIC', options),
|
||||
isError: true,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -131,7 +132,7 @@ export default function RegistrationPage() {
|
||||
<Input
|
||||
id="displayName"
|
||||
type="text"
|
||||
maxLength={24}
|
||||
maxLength={MAX_DISPLAY_NAME_LENGTH}
|
||||
aria-invalid={!!errors.displayName}
|
||||
{...register('displayName')}
|
||||
/>
|
||||
|
||||
@@ -12,12 +12,12 @@ vi.mock('react-i18next', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function errorResponse(status, code, headers = {}) {
|
||||
function errorResponse(status, code, headers = {}, message = undefined) {
|
||||
return {
|
||||
ok: false,
|
||||
status,
|
||||
headers: new Headers(headers),
|
||||
json: async () => ({code}),
|
||||
json: async () => ({code, message}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,4 +60,13 @@ describe('RegistrationPage', () => {
|
||||
|
||||
expect(await screen.findByText('REGISTRATION.ERROR_INVALID_INVITE_CODE')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows its own text, not the English one of the server, for a refused input', async () => {
|
||||
globalThis.fetch = vi.fn(async () => errorResponse(400, 'VALIDATION_FAILED', {}, 'The request is not valid'));
|
||||
|
||||
submitValidForm();
|
||||
|
||||
expect(await screen.findByText('REGISTRATION.ERROR_GENERIC')).toBeTruthy();
|
||||
expect(screen.queryByText('The request is not valid')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -32,6 +32,7 @@ import {apiFetch, refreshSession} from '../utils/api.js';
|
||||
import {hasSession, markSession} from '../utils/auth.js';
|
||||
import {FAVORITE_PLATFORMS, saveFavoritePlatform} from '../utils/platforms.js';
|
||||
import {LANGUAGES, saveSettings} from '../utils/settings.js';
|
||||
import {MAX_DISPLAY_NAME_LENGTH} from '../utils/limits.js';
|
||||
|
||||
const BACKEND_ERROR_KEY = {
|
||||
DISPLAY_NAME_ALREADY_IN_USE: 'SETTINGS.ERROR_DISPLAY_NAME_IN_USE',
|
||||
@@ -158,7 +159,7 @@ export default function SettingsPage() {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
const mappedKey = BACKEND_ERROR_KEY[data.code];
|
||||
toast.error(mappedKey ? t(mappedKey) : data.message || t('SETTINGS.ERROR_SAVE'));
|
||||
toast.error(t(mappedKey ?? 'SETTINGS.ERROR_SAVE'));
|
||||
return {ok: false};
|
||||
}
|
||||
|
||||
@@ -393,7 +394,7 @@ export default function SettingsPage() {
|
||||
<Input
|
||||
id="displayName"
|
||||
type="text"
|
||||
maxLength={24}
|
||||
maxLength={MAX_DISPLAY_NAME_LENGTH}
|
||||
placeholder={t('SETTINGS.DISPLAY_NAME_PLACEHOLDER')}
|
||||
aria-invalid={!!errors.displayName}
|
||||
{...register('displayName')}
|
||||
|
||||
Reference in new issue
Block a user