2.1 KiB
2.1 KiB
Phone Number Formatting
All phone numbers in SPQ are stored as raw digits (no formatting) and displayed/input as 555-555-5555 (hyphen-separated, no parentheses).
Shared Utility: src/lib/phone.ts
Two exports:
-
formatPhoneInput(raw: string): string— live input mask for onChange. Strips non-digits, inserts hyphens as user types.""→"""555"→"555""5555"→"555-5""5555555555"→"555-555-5555"
-
formatPhoneDisplay(phone: string | null | undefined): string— display formatter for read-only contexts (tables, PDFs, previews)."5555555555"→"555-555-5555""5551234"→"555-1234"(7-digit fallback)- Handles legacy formatted data (strips parens, spaces, dots).
Input Pattern (onChange + value)
For every phone <input>:
import { formatPhoneInput } from '../lib/phone';
// In the input:
value={formatPhoneInput(phoneValue)}
onChange={(e) => setPhone(e.target.value.replace(/\D/g, ''))}
placeholder="555-555-5555"
This stores raw digits (stripped on every keystroke) and displays the mask.
Display Pattern
For every read-only phone render:
import { formatPhoneDisplay } from '../lib/phone';
// In JSX:
{formatPhoneDisplay(customer.phone)}
// In template literals:
`${formatPhoneDisplay(inv.customerPhone)}`
Files Using Phone Formatting
| File | Applied |
|---|---|
QuoteGenerator.tsx |
CustomerInfoPanel phone field |
ROForm.tsx |
RO create/edit phone field |
Appointments.tsx |
3 inputs + list display |
Invoices.tsx |
1 input + printable HTML + preview |
Customers.tsx |
1 input + 2 displays (card + detail) |
Pitfalls
- Stored data may be legacy-formatted —
formatPhoneDisplayandformatPhoneInputboth strip non-digits first, so legacy(555) 555-5555data is handled. - Don't store the formatted value — the
onChangehandler must strip formatting (.replace(/\D/g, '')) so PocketBase only contains raw digits. - Don't create local formatPhone functions — always import from
src/lib/phone.ts. Duplicate implementations exist in legacy code and should be replaced.