Add member address, state normalization, account inheritance, fix member form
- Address field on member table (jsonb, same format as account) - Members inherit email, phone, address from account when not provided - State normalization: "Texas" → "TX", "california" → "CA" via shared util - Member form drops zodResolver to fix optional field validation flashing - Account name auto-format: "First Last - Account" - US state lookup with full name + code support
This commit is contained in:
43
packages/shared/__tests__/utils/states.test.ts
Normal file
43
packages/shared/__tests__/utils/states.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
import { normalizeStateCode, isValidStateCode } from '../../src/utils/states.js'
|
||||
|
||||
describe('normalizeStateCode', () => {
|
||||
it('normalizes two-letter codes', () => {
|
||||
expect(normalizeStateCode('tx')).toBe('TX')
|
||||
expect(normalizeStateCode('TX')).toBe('TX')
|
||||
expect(normalizeStateCode('Tx')).toBe('TX')
|
||||
expect(normalizeStateCode(' ca ')).toBe('CA')
|
||||
})
|
||||
|
||||
it('normalizes full state names', () => {
|
||||
expect(normalizeStateCode('Texas')).toBe('TX')
|
||||
expect(normalizeStateCode('texas')).toBe('TX')
|
||||
expect(normalizeStateCode('CALIFORNIA')).toBe('CA')
|
||||
expect(normalizeStateCode('New York')).toBe('NY')
|
||||
expect(normalizeStateCode('north carolina')).toBe('NC')
|
||||
})
|
||||
|
||||
it('returns null for invalid input', () => {
|
||||
expect(normalizeStateCode('ZZ')).toBeNull()
|
||||
expect(normalizeStateCode('Narnia')).toBeNull()
|
||||
expect(normalizeStateCode('')).toBeNull()
|
||||
})
|
||||
|
||||
it('handles territories', () => {
|
||||
expect(normalizeStateCode('DC')).toBe('DC')
|
||||
expect(normalizeStateCode('Puerto Rico')).toBe('PR')
|
||||
expect(normalizeStateCode('GU')).toBe('GU')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isValidStateCode', () => {
|
||||
it('returns true for valid codes', () => {
|
||||
expect(isValidStateCode('TX')).toBe(true)
|
||||
expect(isValidStateCode('ca')).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for invalid codes', () => {
|
||||
expect(isValidStateCode('ZZ')).toBe(false)
|
||||
expect(isValidStateCode('Texas')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,2 +1,3 @@
|
||||
export { formatCurrency, roundCurrency, toCents, toDollars } from './currency.js'
|
||||
export { capBillingDay, todayISO, isMinor } from './dates.js'
|
||||
export { US_STATES, STATE_CODES, isValidStateCode, normalizeStateCode } from './states.js'
|
||||
|
||||
38
packages/shared/src/utils/states.ts
Normal file
38
packages/shared/src/utils/states.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export const US_STATES: Record<string, string> = {
|
||||
AL: 'Alabama', AK: 'Alaska', AZ: 'Arizona', AR: 'Arkansas', CA: 'California',
|
||||
CO: 'Colorado', CT: 'Connecticut', DE: 'Delaware', FL: 'Florida', GA: 'Georgia',
|
||||
HI: 'Hawaii', ID: 'Idaho', IL: 'Illinois', IN: 'Indiana', IA: 'Iowa',
|
||||
KS: 'Kansas', KY: 'Kentucky', LA: 'Louisiana', ME: 'Maine', MD: 'Maryland',
|
||||
MA: 'Massachusetts', MI: 'Michigan', MN: 'Minnesota', MS: 'Mississippi', MO: 'Missouri',
|
||||
MT: 'Montana', NE: 'Nebraska', NV: 'Nevada', NH: 'New Hampshire', NJ: 'New Jersey',
|
||||
NM: 'New Mexico', NY: 'New York', NC: 'North Carolina', ND: 'North Dakota', OH: 'Ohio',
|
||||
OK: 'Oklahoma', OR: 'Oregon', PA: 'Pennsylvania', RI: 'Rhode Island', SC: 'South Carolina',
|
||||
SD: 'South Dakota', TN: 'Tennessee', TX: 'Texas', UT: 'Utah', VT: 'Vermont',
|
||||
VA: 'Virginia', WA: 'Washington', WV: 'West Virginia', WI: 'Wisconsin', WY: 'Wyoming',
|
||||
DC: 'District of Columbia', PR: 'Puerto Rico', VI: 'Virgin Islands', GU: 'Guam',
|
||||
AS: 'American Samoa', MP: 'Northern Mariana Islands',
|
||||
}
|
||||
|
||||
export const STATE_CODES = Object.keys(US_STATES)
|
||||
|
||||
export function isValidStateCode(code: string): boolean {
|
||||
return code.toUpperCase().trim() in US_STATES
|
||||
}
|
||||
|
||||
const STATE_NAME_TO_CODE: Record<string, string> = Object.fromEntries(
|
||||
Object.entries(US_STATES).map(([code, name]) => [name.toLowerCase(), code]),
|
||||
)
|
||||
|
||||
export function normalizeStateCode(input: string): string | null {
|
||||
const trimmed = input.trim()
|
||||
const upper = trimmed.toUpperCase()
|
||||
|
||||
// Direct code match (TX, CA, etc.)
|
||||
if (upper in US_STATES) return upper
|
||||
|
||||
// Full name match (Texas, california, etc.)
|
||||
const byName = STATE_NAME_TO_CODE[trimmed.toLowerCase()]
|
||||
if (byName) return byName
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user