feat: add the account phone numbers block with input-otp and phone-input primitives - #42
Conversation
InputOtp is a one-box-per-digit code field that handles typing, Backspace, arrows, paste, and one-time-code autofill as one value. PhoneInput pairs a searchable country picker with a national-format field that emits E.164, defaults to the browser's language region, and exports formatting and flag helpers. Flags load in their own chunk behind a same-size placeholder. react-phone-number-input is an optional peer, so phone-input is subpath-only and the root barrel never requires it; the parity check enforces that. ComboboxPopup gains an align passthrough.
A settings card to add phone numbers, text them a code, verify, and remove them. The host supplies a five-call adapter; the block owns the flow and scopes every outcome to the card, the add form, or the row that caused it. Backend error codes (including GraphQL extensions.code) map to overridable messages, identityKey remounts per account, and the view and hook are exported for custom layouts. Adds the registry item, catalog entry, install smoke case, and agent skill routing.
The docs page previews every state of the block, plus a live mode backed by an in-memory adapter, at wide and narrow widths. Account is linked from the sidebar, topbar install action, sitemap, llms.txt, and the Authentication feature pack page.
The real block renders its mixed-numbers state beside Recovery & sessions, so themes preview its flags, statuses, and add field. Project icon moves one column over to keep the wall's columns balanced.
|
Review complete. 🟡 1 medium 💬 Inline comments (1)
This PR introduces a full phone-number management block for the account showcase — add, verify (OTP), and remove flows — backed by a new
One medium-severity issue was confirmed: unguarded Reviewed commit: 0f60cdb |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Adds an account phone-numbers block with OTP verification plus PhoneInput/InputOtp components, docs pages, and registry/parity tooling updates.
Key findings
- 🟡
parsePhoneNumberthrows on unparseable numbers — phone-input.tsx:129
| /** The country calling code a number carries, e.g. +1, +44, +353; '' when it cannot be parsed. */ | ||
| function phoneCallingCode(e164: string): string { | ||
| const code = BasePhoneInput.parsePhoneNumber(e164)?.countryCallingCode; | ||
| return code ? `+${code}` : ''; | ||
| } |
There was a problem hiding this comment.
🟡 bug · medium
parsePhoneNumber throws on unparseable numbers
phoneCallingCode calls BasePhoneInput.parsePhoneNumber(e164) without a try/catch (packages/ui/src/components/phone-input.tsx:131), but parsePhoneNumber throws a ParseError for unparseable input instead of returning undefined — so the documented '' fallback is unreachable and the new test asserting phoneCallingCode('garbage') === '' (packages/ui/test/phone-input.test.tsx:47) throws instead of passing. CountryFlag (line 257) has the same unguarded call and renders arbitrary stored numbers from the adapter, so a malformed number stored server-side crashes the row — and the whole card — during render, for exactly the inputs the globe fallback exists for.
📋 Prompt for AI Agents
In packages/ui/src/components/phone-input.tsx, wrap the BasePhoneInput.parsePhoneNumber calls in phoneCallingCode (line 131) and CountryFlag (line 257) in try/catch blocks that return '' (phoneCallingCode) or undefined (CountryFlag) on a thrown ParseError, so the documented ''/globe fallbacks actually trigger on unparseable input. libphonenumber-js's parsePhoneNumber throws ParseError on garbage instead of returning undefined, and packages/ui/test/phone-input.test.tsx line 47 currently asserts behavior the code cannot satisfy.
Summary
Ports the phone numbers settings from the dashboard (constructive-io/dashboard#273) into Blocks so anyone can install it:
@constructive-io/ui: two new primitives, each an npm subpath and a registry item.input-otp: one box per digit; handles typing, Backspace, arrows, paste, and one-time-code autofill as one value; compactsmsize.phone-input: searchable country picker with flags and a national-format field that emits E.164. It defaults to the browser's language region and exportsformatPhoneNumber,normalizePhoneNumber,phoneCallingCode,CountryFlag, anddetectCountry. Picker labels can be localized.react-phone-number-inputis an optional peer. Becausephone-inputimports it directly,phone-inputis subpath-only: the root barrel never requires the peer, and the parity check enforces this.ComboboxPopupgains analignpassthrough.account-phone-numbersblock (apps/blocks):list,add,sendCode,verify,remove). The block owns the add, text-a-code, verify, and remove flow and makes no requests itself.extensions.code, map to overridable messages. ThrowingAccountPhoneNumbersUnavailableErrorshows a notice instead of the list.identityKeyremounts the block per account.AccountPhoneNumbersViewanduseAccountPhoneNumbersare exported for custom layouts; spreading the hook's result into the view keeps custom messages.123456), and wide and narrow widths.llms.txt, and the Authentication feature pack page.Test plan
pnpm checkpnpm build:pages, including the static/blocks/accountand/blocks/account/account-phone-numberspagespnpm build:storybook, with the newPhoneInputandInputOtpstoriespnpm pack:check: all 90 ESM and CJS entries resolve, includingphone-inputpnpm --filter @constructive-io/registry buildand theaccount-phone-numberssmoke install, which typechecks and compiles CSS in a clean consumer that hasstrictNullChecksoffphone-inputandinput-otp