Skip to content

Commit

Permalink
feat: add phone number formatting and validation
Browse files Browse the repository at this point in the history
  • Loading branch information
serikjensen committed Feb 14, 2025
1 parent 78799c9 commit 059c529
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import {
import { Schemas } from '@/types/schema'
import { companyEvents } from '@/shared/constants'
import { RequireAtLeastOne } from '@/types/Helpers'
import { normalizePhone } from '@/helpers/phone'
import { type CreateSignatoryInputs, generateCreateSignatorySchema } from './CreateSignatoryForm'
import { CreateSignatoryForm } from './CreateSignatoryForm'
import { Actions } from './Actions'

import styles from './CreateSignatory.module.scss'

export const SignatoryAssignmentMode = {
create_signatory: 'create_signatory',
invite_signatory: 'invite_signatory',
Expand Down Expand Up @@ -91,7 +94,7 @@ function Root({
last_name: currentSignatory?.last_name ?? defaultValues?.last_name ?? '',
email: currentSignatory?.email ?? defaultValues?.email ?? '',
title: currentSignatory?.title ?? defaultValues?.title ?? '',
phone: currentSignatory?.phone ?? defaultValues?.phone ?? '',
phone: normalizePhone(currentSignatory?.phone ?? defaultValues?.phone ?? ''),
ssn: currentSignatory?.has_ssn ? '' : defaultValues?.ssn,
street_1: currentSignatory?.home_address?.street_1 ?? defaultValues?.street_1,
street_2: currentSignatory?.home_address?.street_2 ?? defaultValues?.street_2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { useTranslation } from 'react-i18next'
import { ListBoxItem } from 'react-aria-components'
import { TextField, Grid, Flex, Select } from '@/components/Common'
import { DatePicker } from '@/components/Common/Inputs/DatePicker'
import { nameValidation, zipValidation, SSN_REGEX } from '@/helpers/validations'
import { nameValidation, zipValidation, SSN_REGEX, phoneValidation } from '@/helpers/validations'
import { STATES_ABBR } from '@/shared/constants'
import { normalizeSSN, usePlaceholderSSN } from '@/helpers/ssn'
import { CalendarDate } from '@internationalized/date'
import { TitleSelect } from '@/components/Company/AssignSignatory/TitleSelect'
import { normalizePhone } from '@/helpers/phone'
import { useCreateSignatory } from './CreateSignatory'

const createSSNValidation = (currentSignatory?: { has_ssn?: boolean }) =>
Expand All @@ -34,7 +35,7 @@ export const generateCreateSignatorySchema = (currentSignatory?: { has_ssn?: boo
last_name: nameValidation,
email: v.pipe(v.string(), v.nonEmpty(), v.email()),
title: v.pipe(v.string(), v.nonEmpty()),
phone: v.pipe(v.string(), v.nonEmpty()),
phone: phoneValidation,
ssn: createSSNValidation(currentSignatory),
birthday: v.instance(CalendarDate),
street_1: v.pipe(v.string(), v.nonEmpty()),
Expand Down Expand Up @@ -90,8 +91,13 @@ export const CreateSignatoryForm = () => {
control={control}
name="phone"
label={t('signatoryDetails.phone')}
errorMessage={t('validations.phone')}
isRequired
errorMessage={t('validations.phone')}
inputProps={{
onChange: event => {
setValue('phone', normalizePhone(event.target.value))
},
}}
/>
<TextField
control={control}
Expand Down
40 changes: 40 additions & 0 deletions src/helpers/phone.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from 'vitest'
import { normalizePhone, removeNonDigits } from './phone'

describe('phone', () => {
describe('removeNonDigits', () => {
it('should return empty string for empty input', () => {
expect(removeNonDigits('')).toBe('')
})

it('should remove non-digits', () => {
expect(removeNonDigits('a12-34/5 6b_78@90)')).toBe('1234567890')
})
})

describe('normalizePhone', () => {
it('should return empty string for empty input', () => {
expect(normalizePhone('')).toBe('')
})

it('should format first 3 digits with parenthesis', () => {
expect(normalizePhone('123')).toBe('(123')
})

it('should add space and format next 3 digits', () => {
expect(normalizePhone('123456')).toBe('(123) 456')
})

it('should add hyphen when exceeding 6 digits', () => {
expect(normalizePhone('1234567')).toBe('(123) 456-7')
})

it('should strip non-digits before formatting', () => {
expect(normalizePhone('(123) abc 456-7890')).toBe('(123) 456-7890')
})

it('should truncate input longer than 10 digits', () => {
expect(normalizePhone('12345678901234')).toBe('(123) 456-7890')
})
})
})
20 changes: 20 additions & 0 deletions src/helpers/phone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const removeNonDigits = (value: string): string => {
return value.replace(/\D/g, '')
}

export const normalizePhone = (value: string): string => {
const digits = removeNonDigits(value)

if (!digits.length) return ''

// Format: (XXX) XXX-XXXX
if (digits.length <= 3) {
return `(${digits}`
}

if (digits.length <= 6) {
return `(${digits.slice(0, 3)}) ${digits.slice(3)}`
}

return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6, 10)}`
}
10 changes: 10 additions & 0 deletions src/helpers/validations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as v from 'valibot'
import { normalizePhone, removeNonDigits } from '@/helpers/phone'

export const NAME_REGEX = /^([a-zA-Z\xC0-\uFFFF]+([ \-']{0,1}[a-zA-Z\xC0-\uFFFF]+)*[.]{0,1}){1,2}$/

Expand All @@ -10,3 +11,12 @@ export const zipValidation = v.pipe(
)

export const SSN_REGEX = /^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$/

export const phoneValidation = v.pipe(
v.string(),
v.transform(normalizePhone),
v.check(phone => {
const digits = removeNonDigits(phone)
return digits.length === 10
}),
)
2 changes: 1 addition & 1 deletion src/i18n/en/Company.AssignSignatory.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"emailMismatch": "Email addresses must match",
"firstName": "First name is required",
"lastName": "Last name is required",
"phone": "Phone number is required",
"phone": "Phone number must be 10 digits",
"title": "Title is required",
"address": {
"street1": "Street address is required",
Expand Down

0 comments on commit 059c529

Please sign in to comment.