/ src / utils / validate.ts
validate.ts
  1  export type FormData = {
  2    name: string
  3    email: string
  4    address1: string
  5    address2: string
  6    city: string
  7    state: string
  8    zip: string
  9    phone: string
 10    usLocation: boolean
 11  }
 12  
 13  export type ValidationError = {
 14    message: string
 15  }
 16  
 17  export function validateField(
 18    field: keyof FormData,
 19    value: string,
 20  ): ValidationError | null {
 21    // Trim whitespace for validation
 22    const trimmed = value.trim()
 23  
 24    if (!trimmed && field === 'address2') {
 25      return null // address2 is optional
 26    }
 27  
 28    // Basic required field check
 29    if (!trimmed) {
 30      return { message: 'This field is required' }
 31    }
 32  
 33    switch (field) {
 34      case 'email': {
 35        const emailRegex =
 36          /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
 37        if (!emailRegex.test(trimmed)) {
 38          return { message: 'Please enter a valid email address' }
 39        }
 40        break
 41      }
 42  
 43      case 'name':
 44        if (trimmed.length < 2) {
 45          return { message: 'Name must be at least 2 characters long' }
 46        }
 47        break
 48  
 49      case 'address1': {
 50        if (trimmed.length < 3) {
 51          return { message: 'Please enter a valid address' }
 52        }
 53        // Accept PO Box format or regular street address
 54        const isPOBox = /^P\.?O\.?\s*Box\s+\d+$/i.test(trimmed)
 55        const hasNumber = /\d+/.test(trimmed)
 56        if (!isPOBox && !hasNumber) {
 57          return { message: 'Please include a number in the street address' }
 58        }
 59        break
 60      }
 61      case 'address2':
 62        break
 63  
 64      case 'city':
 65        if (trimmed.length < 2) {
 66          return { message: 'City name must be at least 2 characters long' }
 67        }
 68        if (!/^[a-zA-Z\s.-]+$/.test(trimmed)) {
 69          return {
 70            message:
 71              'City can only contain letters, spaces, periods, and hyphens',
 72          }
 73        }
 74        break
 75  
 76      case 'state': {
 77        const states = new Set([
 78          'AL',
 79          'AK',
 80          'AZ',
 81          'AR',
 82          'CA',
 83          'CO',
 84          'CT',
 85          'DE',
 86          'FL',
 87          'GA',
 88          'HI',
 89          'ID',
 90          'IL',
 91          'IN',
 92          'IA',
 93          'KS',
 94          'KY',
 95          'LA',
 96          'ME',
 97          'MD',
 98          'MA',
 99          'MI',
100          'MN',
101          'MS',
102          'MO',
103          'MT',
104          'NE',
105          'NV',
106          'NH',
107          'NJ',
108          'NM',
109          'NY',
110          'NC',
111          'ND',
112          'OH',
113          'OK',
114          'OR',
115          'PA',
116          'RI',
117          'SC',
118          'SD',
119          'TN',
120          'TX',
121          'UT',
122          'VT',
123          'VA',
124          'WA',
125          'WV',
126          'WI',
127          'WY',
128          'DC',
129        ])
130        const stateCode = trimmed.toUpperCase()
131        if (!states.has(stateCode)) {
132          return { message: 'Please enter a valid US state code (e.g. CA)' }
133        }
134        break
135      }
136  
137      case 'usLocation': {
138        const normalized = trimmed.toLowerCase()
139        if (!['y', 'yes', 'n', 'no'].includes(normalized)) {
140          return { message: 'Please enter y/yes or n/no' }
141        }
142        break
143      }
144  
145      case 'zip':
146        // ZIP code validation for US
147        if (!/^\d{5}(-\d{4})?$/.test(trimmed)) {
148          return {
149            message: 'Please enter a valid ZIP code (e.g. 12345 or 12345-6789)',
150          }
151        }
152        break
153  
154      case 'phone':
155        // Phone validation for US (allow various formats)
156        if (!/^(\+1\s?)?(\d{3}[-.\s]??)?\d{3}[-.\s]??\d{4}$/.test(trimmed)) {
157          return {
158            message: 'Please enter a valid US phone number',
159          }
160        }
161        break
162    }
163  
164    return null
165  }