Amba-Agent-App/lib/utils/validationSchemas.ts
2026-01-16 00:22:35 +03:00

188 lines
5.5 KiB
TypeScript

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<any>): 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 = <T extends v.BaseSchema<any, any, any>>(
schema: T,
input: unknown
): { success: true; data: v.InferInput<T> } | { 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' };
}
};