import * as v from 'valibot'; import { isValidPhoneNumber } from './phoneUtils'; // Common validation schemas export const fullNameSchema = v.pipe( v.string(), v.minLength(1, 'Please enter your full name'), v.trim(), v.minLength(1, 'Full name cannot be empty') ); export const emailSchema = v.pipe( v.string(), v.minLength(1, 'Please enter your email address'), v.email('Please enter a valid email address'), v.trim() ); export const phoneNumberSchema = v.pipe( v.string(), v.minLength(1, 'Please enter a phone number'), v.trim(), v.custom((value) => { if (typeof value !== 'string') return false; const cleanPhone = value.replace(/[^+\d]/g, ''); if (cleanPhone.length < 7) { return false; } // Use the existing phone validation utility return isValidPhoneNumber(value); }, 'Please enter a valid phone number') ); export const pinSchema = v.pipe( v.string(), v.minLength(1, 'Please enter a PIN'), v.length(6, 'PIN must be exactly 6 digits'), v.regex(/^\d{6}$/, 'PIN must contain only digits') ); export const confirmPinSchema = (pin: string) => v.pipe( v.string(), v.minLength(1, 'Please confirm your PIN'), v.custom((value) => { return typeof value === 'string' && value === pin; }, 'PIN and confirm PIN do not match') ); export const addressSchema = v.optional( v.pipe( v.string(), v.trim() ) ); export const cardNumberSchema = v.pipe( v.string(), v.minLength(1, 'Card number is required'), v.trim(), v.custom((value) => { if (typeof value !== 'string') return false; const cleanNumber = value.replace(/\s/g, ''); // Basic card number validation (13-19 digits) return /^\d{13,19}$/.test(cleanNumber); }, 'Please enter a valid card number') ); export const expiryDateSchema = v.pipe( v.string(), v.minLength(1, 'Expiry date is required'), v.trim(), v.custom((value) => { if (typeof value !== 'string') return false; // Validate MM/YY format if (!/^\d{2}\/\d{2}$/.test(value)) { return false; } const [month, year] = value.split('/'); const monthNum = parseInt(month, 10); const yearNum = parseInt(year, 10); if (monthNum < 1 || monthNum > 12) { return false; } // Check if card is expired (basic check - assumes 20XX) const currentYear = new Date().getFullYear() % 100; const currentMonth = new Date().getMonth() + 1; if (yearNum < currentYear || (yearNum === currentYear && monthNum < currentMonth)) { return false; } return true; }, 'Please enter a valid expiry date') ); export const cvvSchema = v.pipe( v.string(), v.minLength(1, 'CVV is required'), v.trim(), v.regex(/^\d{3,4}$/, 'CVV must be 3 or 4 digits') ); // Amount validation schemas export const amountSchema = (options?: { min?: number; // in cents max?: number; // in cents minDisplay?: string; // for error messages maxDisplay?: string; // for error messages }) => { const min = options?.min ?? 1; // Default 1 cent const max = options?.max ?? 99999; // Default $999.99 const minDisplay = options?.minDisplay ?? '$0.01'; const maxDisplay = options?.maxDisplay ?? '$999.99'; return v.pipe( v.string(), v.custom((value) => { if (typeof value !== 'string') return false; return !(!value || value === '' || value === '0' || value === '0.' || value === '0.00'); }, 'Please enter an amount'), v.custom((value) => { if (typeof value !== 'string') return false; // Parse amount to cents const numValue = parseFloat(value); if (isNaN(numValue)) { return false; } const amountInCents = Math.round(numValue * 100); return amountInCents >= min && amountInCents <= max; }, `Amount must be between ${minDisplay} and ${maxDisplay}`) ); }; // Form schemas export const phoneSetupSchema = v.object({ fullName: fullNameSchema, email: emailSchema, address: addressSchema, pin: pinSchema, confirmPin: v.string(), // Will be validated separately with confirmPinSchema }); export const addRecipientSchema = v.object({ fullName: fullNameSchema, phoneNumber: phoneNumberSchema, }); export const addCardSchema = v.object({ cardNumber: cardNumberSchema, expiryDate: expiryDateSchema, cvv: cvvSchema, }); export const profileUpdateSchema = v.object({ fullName: fullNameSchema, phoneNumber: v.optional(phoneNumberSchema), email: v.optional(emailSchema), address: addressSchema, }); // Helper function to get validation errors export const getValidationError = (error: v.ValiError): string => { if (error.issues && error.issues.length > 0) { return error.issues[0].message || 'Validation error'; } return 'Validation error'; }; // Helper function to validate and get errors export const validate = >( schema: T, input: unknown ): { success: true; data: v.InferInput } | { success: false; error: string } => { try { const data = v.parse(schema, input); return { success: true, data }; } catch (error) { if (error instanceof v.ValiError) { return { success: false, error: getValidationError(error) }; } return { success: false, error: 'Validation error' }; } };