pwf-website-new/client/src/lib/utils/form-validation.ts

155 lines
4.1 KiB
TypeScript

import { z } from "zod";
import { insertUserSchema, insertContactMessageSchema, insertNewsletterSchema } from "@shared/schema";
/**
* Extended registration schema with password confirmation
*/
export const registrationSchema = insertUserSchema.extend({
password: z.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
.regex(/[0-9]/, "Password must contain at least one number"),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
/**
* Contact form schema with validation
*/
export const contactFormSchema = insertContactMessageSchema.extend({
name: z.string()
.min(2, "Name must be at least 2 characters")
.max(100, "Name cannot exceed 100 characters"),
email: z.string()
.email("Please enter a valid email address"),
subject: z.string()
.min(2, "Subject must be at least 2 characters")
.max(200, "Subject cannot exceed 200 characters")
.optional(),
message: z.string()
.min(10, "Message must be at least 10 characters")
.max(2000, "Message cannot exceed 2000 characters"),
});
/**
* Newsletter signup schema with validation
*/
export const newsletterSchema = insertNewsletterSchema.extend({
email: z.string()
.email("Please enter a valid email address"),
agreedToTerms: z.boolean()
.refine(val => val === true, {
message: "You must agree to receive emails",
}),
});
/**
* Booking form schema
*/
export const bookingFormSchema = z.object({
classId: z.number({
required_error: "Please select a class",
}),
date: z.date({
required_error: "Please select a date",
}),
time: z.string({
required_error: "Please select a time",
}),
});
/**
* Email validation function
*/
export function validateEmail(email: string): boolean {
return z.string().email().safeParse(email).success;
}
/**
* Password strength validation
* Returns a score from 0-4 and feedback
*/
export function validatePasswordStrength(password: string): {
score: number;
feedback: string;
} {
// Start with a score of 0
let score = 0;
let feedback = "Password is too weak";
// If password is empty, return early
if (!password) {
return { score, feedback };
}
// Add points for length
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
// Add points for complexity
if (/[A-Z]/.test(password)) score += 0.5;
if (/[a-z]/.test(password)) score += 0.5;
if (/[0-9]/.test(password)) score += 0.5;
if (/[^A-Za-z0-9]/.test(password)) score += 0.5;
// Provide feedback based on score
if (score < 1) {
feedback = "Password is too weak";
} else if (score < 2) {
feedback = "Password is weak";
} else if (score < 3) {
feedback = "Password is moderate";
} else if (score < 4) {
feedback = "Password is strong";
} else {
feedback = "Password is very strong";
}
// Ensure score is between 0-4
score = Math.min(4, Math.max(0, Math.floor(score)));
return { score, feedback };
}
/**
* Format phone number for display
*/
export function formatPhoneNumber(phoneNumber: string): string {
// Remove all non-digits
const cleaned = phoneNumber.replace(/\D/g, '');
// Format based on length
if (cleaned.length === 10) {
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6, 10)}`;
} else if (cleaned.length === 11) {
return `+${cleaned.slice(0, 1)} (${cleaned.slice(1, 4)}) ${cleaned.slice(4, 7)}-${cleaned.slice(7, 11)}`;
} else if (cleaned.length === 12) {
return `+${cleaned.slice(0, 2)} (${cleaned.slice(2, 5)}) ${cleaned.slice(5, 8)}-${cleaned.slice(8, 12)}`;
}
// Return original if we can't format it
return phoneNumber;
}
/**
* Format price for display (e.g., $25.00)
*/
export function formatPrice(priceInCents: number): string {
return `$${(priceInCents / 100).toFixed(2)}`;
}
/**
* Validate URL function
*/
export function validateUrl(url: string): boolean {
try {
new URL(url);
return true;
} catch {
return false;
}
}