- Complete Dashboard page with statistics, recent activity, compliance status - Complete Transactions page with form, validation, E&O uplift display - Complete Treasury page with account management - Complete Reports page with BCB report generation and export - Add LoadingSpinner component - Add ErrorBoundary component - Add Toast notification system - Add comprehensive input validation - Add error handling utilities - Add basic unit tests structure - Fix XML exporter TypeScript errors - All quick wins completed
257 lines
5.8 KiB
TypeScript
257 lines
5.8 KiB
TypeScript
/**
|
|
* Input validation utilities for user inputs and transaction data
|
|
*/
|
|
|
|
import { validateBrazilianTaxId } from './validation';
|
|
|
|
export interface ValidationResult {
|
|
valid: boolean;
|
|
errors: string[];
|
|
warnings: string[];
|
|
}
|
|
|
|
/**
|
|
* Validate transaction amount
|
|
*/
|
|
export function validateAmount(amount: number | string): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
|
|
if (isNaN(numAmount)) {
|
|
errors.push('Amount must be a valid number');
|
|
} else if (numAmount <= 0) {
|
|
errors.push('Amount must be greater than zero');
|
|
} else if (numAmount > 1000000000) {
|
|
warnings.push('Amount exceeds 1 billion - please verify');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate currency code (ISO 4217)
|
|
*/
|
|
export function validateCurrency(currency: string): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!currency || currency.trim().length === 0) {
|
|
errors.push('Currency code is required');
|
|
} else if (currency.length !== 3) {
|
|
errors.push('Currency code must be 3 characters (ISO 4217)');
|
|
} else if (!/^[A-Z]{3}$/.test(currency)) {
|
|
errors.push('Currency code must be uppercase letters only');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate email address
|
|
*/
|
|
export function validateEmail(email: string | undefined): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!email) {
|
|
return { valid: true, errors, warnings };
|
|
}
|
|
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
errors.push('Invalid email address format');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate phone number (basic validation)
|
|
*/
|
|
export function validatePhone(phone: string | undefined): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!phone) {
|
|
return { valid: true, errors, warnings };
|
|
}
|
|
|
|
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
|
|
if (!phoneRegex.test(phone)) {
|
|
errors.push('Invalid phone number format');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate account number or IBAN
|
|
*/
|
|
export function validateAccountNumber(
|
|
accountNumber: string | undefined,
|
|
iban: string | undefined
|
|
): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!accountNumber && !iban) {
|
|
errors.push('Either account number or IBAN is required');
|
|
return { valid: false, errors, warnings };
|
|
}
|
|
|
|
if (iban) {
|
|
// Basic IBAN validation (2 letters + 2 digits + up to 30 alphanumeric)
|
|
const ibanRegex = /^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/;
|
|
if (!ibanRegex.test(iban.replace(/\s/g, ''))) {
|
|
errors.push('Invalid IBAN format');
|
|
}
|
|
}
|
|
|
|
if (accountNumber && accountNumber.trim().length === 0) {
|
|
errors.push('Account number cannot be empty');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate purpose of payment
|
|
*/
|
|
export function validatePurposeOfPayment(
|
|
purpose: string | undefined
|
|
): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!purpose || purpose.trim().length === 0) {
|
|
errors.push('Purpose of payment is required');
|
|
} else if (purpose.trim().length < 5) {
|
|
warnings.push('Purpose of payment is very short - provide more detail');
|
|
} else if (purpose.length > 140) {
|
|
warnings.push('Purpose of payment exceeds 140 characters - may be truncated');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate name field
|
|
*/
|
|
export function validateName(name: string | undefined, fieldName: string): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
errors.push(`${fieldName} is required`);
|
|
} else if (name.trim().length < 2) {
|
|
errors.push(`${fieldName} must be at least 2 characters`);
|
|
} else if (name.length > 140) {
|
|
warnings.push(`${fieldName} exceeds 140 characters`);
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate address
|
|
*/
|
|
export function validateAddress(
|
|
address: string | undefined,
|
|
city: string | undefined,
|
|
country: string | undefined
|
|
): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!address && !city) {
|
|
errors.push('Either address or city is required');
|
|
}
|
|
|
|
if (!country) {
|
|
errors.push('Country is required');
|
|
} else if (country.length !== 2) {
|
|
errors.push('Country must be a 2-letter ISO code');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate tax ID (CPF or CNPJ)
|
|
*/
|
|
export function validateTaxId(taxId: string | undefined, fieldName: string): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!taxId) {
|
|
errors.push(`${fieldName} is required`);
|
|
return { valid: false, errors, warnings };
|
|
}
|
|
|
|
const validation = validateBrazilianTaxId(taxId);
|
|
if (!validation.valid) {
|
|
errors.push(`${fieldName}: ${validation.error || 'Invalid format'}`);
|
|
}
|
|
|
|
return {
|
|
valid: validation.valid,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Sanitize string input (remove dangerous characters)
|
|
*/
|
|
export function sanitizeString(input: string): string {
|
|
return input
|
|
.replace(/[<>]/g, '') // Remove potential HTML/XML tags
|
|
.replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* Sanitize number input
|
|
*/
|
|
export function sanitizeNumber(input: string | number): number {
|
|
if (typeof input === 'number') {
|
|
return isNaN(input) ? 0 : input;
|
|
}
|
|
const num = parseFloat(input.replace(/[^\d.-]/g, ''));
|
|
return isNaN(num) ? 0 : num;
|
|
}
|