Bridge Training
UI Training

Account

Public handle settings, an editable field checked while typing, confirmed as final, then read-only.

Import from @workspace/ui-training/account/*.

AccountHandleViewModel

Prop

Type

AccountHandleAvailabilityViewModel

Where the availability check of the typed handle stands: idle while nothing is typed, checking, available, or unavailable with a reason (too_short, too_long, invalid_format, reserved, taken).

AccountHandleSettings

A controlled field: the app owns the draft, the availability and the write. The component renders the /authors/ prefix, the availability line (a CVA status variant: idle, checking, available, unavailable), the suggestion while the field is empty, and a confirmation dialog: onConfirm is only called once the user confirmed that the handle can no longer be changed. A locked handle (the locked mode) is shown read-only, without the submit, with a notice instead of the availability line.

PropRole
accountAccountHandleViewModel: the stored handle and its lock
value, onValueChangeThe draft; ignored once locked
availabilityAccountHandleAvailabilityViewModel; the submit is enabled only when available
suggestion?A handle offered while the field is empty, picked with Use it
isSaving?Disables the field and the submit while the write runs
onConfirmCalled after the confirmation dialog
import { AccountHandleSettings } from '@workspace/ui-training/account/account-handle-settings';

<AccountHandleSettings
  account={{ handle: account.handle, locked: account.handleLocked }}
  value={draft}
  onValueChange={setDraft}
  availability={availability}
  suggestion={suggestion}
  isSaving={setHandle.isPending}
  onConfirm={() => setHandle.mutate(draft)}
/>

AccountHandleSettingsSkeleton (same entry) is its placeholder while the account loads.

In apps/app, features/account/edit/handle maps the account and the availability query to these ViewModels, checks the format locally with the domain rules before asking the server, debounces the server check, and mounts the strings from the account namespace (profile.handle.*). The account page composes it as EditAccount.Handle, next to EditAccount.BasicForm: each block saves on its own, and the page owns their Suspense and skeletons.

Strings

AccountHandleStringsProvider and useAccountHandleStrings live in account/strings. unavailable(reason), suggestion(handle) and confirmTitle(handle) are factories.