Files
bscscore/src/utils/validation.ts
Frank Schwenk 8085d2ecc8 feat(storage): migrate to IndexedDB with localStorage fallback and async app flow
- Add IndexedDB service with schema, indexes, and player stats
- Migrate GameService to async IndexedDB and auto-migrate from localStorage
- Update hooks and App handlers to async; add error handling and UX feedback
- Convert remaining JSX components to TSX
- Add test utility for IndexedDB and migration checks
- Extend game types with sync fields for future online sync
2025-10-30 09:36:17 +01:00

99 lines
2.7 KiB
TypeScript

import { APP_CONFIG, VALIDATION_MESSAGES } from './constants';
import type { NewGameData } from '../types/game';
export interface ValidationResult {
isValid: boolean;
errors: string[];
}
export function validatePlayerName(name: string): ValidationResult {
const errors: string[] = [];
const trimmedName = name.trim();
if (!trimmedName) {
errors.push(VALIDATION_MESSAGES.PLAYER_NAME_REQUIRED);
}
if (trimmedName.length > APP_CONFIG.MAX_PLAYER_NAME_LENGTH) {
errors.push(VALIDATION_MESSAGES.PLAYER_NAME_TOO_LONG);
}
return {
isValid: errors.length === 0,
errors,
};
}
export function validateGameData(data: NewGameData): ValidationResult {
const errors: string[] = [];
try {
// Validate player names
const player1Validation = validatePlayerName(data.player1);
const player2Validation = validatePlayerName(data.player2);
errors.push(...player1Validation.errors);
errors.push(...player2Validation.errors);
// Check for duplicate player names
const playerNames = [data.player1.trim(), data.player2.trim()];
if (data.player3?.trim()) {
const player3Validation = validatePlayerName(data.player3);
errors.push(...player3Validation.errors);
playerNames.push(data.player3.trim());
}
const uniqueNames = new Set(playerNames.filter(name => name.length > 0));
if (uniqueNames.size !== playerNames.filter(name => name.length > 0).length) {
errors.push(VALIDATION_MESSAGES.DUPLICATE_PLAYER_NAMES);
}
// Validate game type
if (!data.gameType?.trim()) {
errors.push(VALIDATION_MESSAGES.GAME_TYPE_REQUIRED);
}
// Validate race to
if (!data.raceTo?.trim()) {
errors.push(VALIDATION_MESSAGES.RACE_TO_REQUIRED);
} else {
const raceToNumber = parseInt(data.raceTo, 10);
if (isNaN(raceToNumber) || raceToNumber <= 0) {
errors.push(VALIDATION_MESSAGES.RACE_TO_INVALID);
}
}
} catch (error) {
console.error('Validation error:', error);
errors.push('Ein unerwarteter Validierungsfehler ist aufgetreten');
}
return {
isValid: errors.length === 0,
errors,
};
}
export function sanitizePlayerName(name: string): string {
return name
.trim()
.slice(0, APP_CONFIG.MAX_PLAYER_NAME_LENGTH)
.replace(/[^\w\s-]/g, ''); // Remove special characters except spaces and hyphens
}
export function validateRaceTo(value: string): ValidationResult {
const errors: string[] = [];
if (!value?.trim()) {
errors.push(VALIDATION_MESSAGES.RACE_TO_REQUIRED);
} else {
const numValue = parseInt(value, 10);
if (isNaN(numValue) || numValue <= 0) {
errors.push(VALIDATION_MESSAGES.RACE_TO_INVALID);
}
}
return {
isValid: errors.length === 0,
errors,
};
}