Skip to content

feat(contacts): shared ContactForm, ContactAvatar, form hooks [PERA-3932]#373

Merged
yasin-ce merged 18 commits intomainfrom
contacts/03-shared-domain
Apr 27, 2026
Merged

feat(contacts): shared ContactForm, ContactAvatar, form hooks [PERA-3932]#373
yasin-ce merged 18 commits intomainfrom
contacts/03-shared-domain

Conversation

@yasin-ce
Copy link
Copy Markdown
Collaborator

@yasin-ce yasin-ce commented Apr 24, 2026

Description

In-module commons shared by add + edit + view flows:

  • `ContactAvatar` placeholder refactored: one-size-smaller icon, `variant='primary'` for dark-mode contrast (fixes PERA-3932).
  • New `ContactForm` shared form: avatar, QR scan, NFD status, name→address focus chain, address whitespace stripping.
  • New form hooks: `useContactForm`, `useAddContactForm`, `useEditContactForm` (with a duplicate-address guard on edit).
  • Shared i18n keys: `contacts.edit_contact.name_label`, `address_label`, `add_photo`, `add_contact`.

Related Issues

Checklist

  • Have you tested your changes locally?
  • Have you reviewed the code for any potential issues?
  • Have you documented any necessary changes in the project's documentation?
  • Have you added any necessary tests for your changes?
  • Have you updated any relevant dependencies?

Additional Notes

Stacked on #372. Covered by: new `ContactForm.spec.tsx` (10 tests), updated `ContactAvatar.spec.tsx` + `AddressDisplay.spec.tsx` (PERA-3932 regression), and three hook specs.

- saveContact: insert-only → upsert (functional set).
- Schema: id and image optional so edits round-trip the id.
- deleteContact: simplified; setContacts removed (unused).
- ConfirmActionBottomSheet: generic confirmation sheet.
- useImagePicker: gallery picker with permission UX; strips EXIF.
- QRScannerView: pad empty state; re-enable scanner after unrecognized
  codes; guard requestPermission against unmount.
- PWIcon: register contacts + pen-solid; drop white fill on moon.
- Add SHORT_ADDRESS_FORMAT (9) to @constants/ui.
- iOS: photo-library permission + expo-image-picker plugin.
- Add expo-image-picker dep.
…RA-3932]

- ContactForm: shared presentational form with avatar, QR-scan button,
  NFD status, children slot. Keeps namePlaceholder and isAddressEditable
  props for backward-compat with unmigrated callers.
- ContactAvatar: cleaner API (no SvgProps leak), placeholder icon sized
  one step smaller than the circle, variant='primary' for dark-mode
  parity (PERA-3932).
- useContactForm / useAddContactForm / useEditContactForm: shared form
  plumbing. Edit hook guards against saving a duplicate address.
- PWInput: add editable, multiline, returnKeyType prop passthroughs.
- AddressDisplay: regression test for default contact icon variant.
- i18n: contacts.edit_contact.add_photo, duplicate_address_error.
@yasin-ce yasin-ce self-assigned this Apr 24, 2026
@yasin-ce yasin-ce requested review from fmsouza, wjbeau and yasincaliskan and removed request for fmsouza and yasincaliskan April 24, 2026 02:59
@yasin-ce yasin-ce marked this pull request as ready for review April 24, 2026 03:05
Comment thread apps/mobile/src/components/ContactForm/ContactForm.tsx Outdated
Comment thread apps/mobile/src/modules/contacts/hooks/useAddContactForm.ts Outdated
Comment thread apps/mobile/src/modules/contacts/hooks/useEditContactForm.ts Outdated
… review

Instead of the UI hooks calling findContacts to guard against duplicate
addresses, saveContact now enforces uniqueness at the store layer and
throws DuplicateAddressError on conflict. Downstream hooks catch and
surface to form state.

Addresses PR #373 review comment to keep business rules in the package,
not the UI layer.
…er review

Replace the Alert.alert call in useImagePicker with a ConfirmActionBottomSheet,
consistent with the rest of the codebase which doesn't use Alert. Extract
PhotoPermissionDeniedSheet as a reusable component wrapping
ConfirmActionBottomSheet with the photo-specific i18n copy. The hook now
returns a permissionDenied state object (isVisible, close, openSettings)
that callers pair with <PhotoPermissionDeniedSheet /> at the screen root.

Also drops custom fontSize/lineHeight from ConfirmActionBottomSheet/styles.ts
in favor of the PWText body variant at the callsite.
- useCallback handleScan / handleOpenScanner / handleCloseScanner in
  ContactForm instead of recreating on every render.
- Replace UI-layer findContacts duplicate check with a try/catch around
  saveContact that surfaces DuplicateAddressError as a form error.
- Forward permissionDenied state (from useImagePicker) through
  useContactForm, so screens can mount PhotoPermissionDeniedSheet.
- When NFD resolution completes, sync rawAddressInput to the resolved
  address so the visible input matches the form state on submit.
- Separate type imports into trailing import type blocks.
Base automatically changed from contacts/02-primitives to main April 25, 2026 00:18
Replace the synthetic 'id' field with address-as-PK. The Contact type
no longer carries 'id'; uniqueness is enforced by address alone. The
single 'saveContact' method splits into two clearer operations:

- addContact(contact): insert; throws DuplicateAddressError if the
  address is already in use.
- editContact(previousAddress, contact): update the row keyed at
  previousAddress regardless of which fields changed; throws
  DuplicateAddressError if renaming into another contact's address.

Form hooks (useAddContactForm, useEditContactForm) and the multisig
EditParticipantScreen flow already used try/catch over the shared API;
they now call the addContact / editContact functions directly. UI no
longer queries-then-checks for duplicates.

Per wjbeau's reviews on PR #373 ("why not make address the primary key
... and have save just throw a DuplicateException").
@yasin-ce yasin-ce requested a review from wjbeau April 25, 2026 01:19
- EditContactScreen.tsx: collapse merge artifact (duplicate useStyles
  imports, duplicate selectedContact/save declarations, stray useEffect
  referencing undefined symbols) into the clean useEditContactForm-based
  version.
- ContactListScreen.tsx: drop c.id ?? c.address (Contact no longer has
  an id field after the address-as-PK refactor); use c.address directly.
- ContactQRBottomSheet.spec.tsx: drop id from the test fixture.
- PWInput: expose rightIconContainerStyle, autoComplete, selectTextOnFocus,
  and align onFocus/onBlur with RNEInputProps so SearchInput can pass them
  through without a TS suppression.
- SearchInput: type SearchInputProps from PWInputProps instead of the
  wider InputProps so the {...props} spread is type-compatible.
Three fixes for runtime/style regressions inherited via the latest
main merge:

- PWText: drop the redundant h1/h2/h3/h4 booleans that were forwarded
  to RNEUI's Text. RNEUI flattens its theme h{n}Style at the END of
  the style chain, which silently overrode any color the caller put
  on `style`. The flags were a no-op for callers using `variant='body'`
  (the default), but PR #382 added `variant='h4'` to PWButton's title
  PWText — making every primary button's title color get stomped to
  textMain. In dark mode buttonPrimaryText (gray[900]) was overridden
  by textMain (gray[100]), producing washed-out text on yellow.
  styles.text already applies the full typography via getTypography,
  so the h{n} flags were both redundant and harmful.

- App.tsx: PR #383 changed the pre-bootstrap loading label from
  <Text> to <PWText>, but PWText's makeStyles needs a ThemeProvider
  in the tree, and the only ThemeProvider lives inside RootComponent
  which only mounts after `bootstrapped && persister`. Crash in
  typography.ts:46 when accessing `theme.colors.textMain`. Wrap the
  pre-bootstrap branch in its own ThemeProvider (using getTheme +
  useIsDarkMode) so PWText has the context it needs while keeping
  the no-primitive-rn-components guardrail satisfied.

- ViewContactScreen styles.ts: replace divider `height: 1` with
  `theme.borders.sm` to satisfy the no-numeric-sizes guardrail
  introduced upstream.
@yasin-ce yasin-ce merged commit f176773 into main Apr 27, 2026
8 checks passed
@yasin-ce yasin-ce deleted the contacts/03-shared-domain branch April 27, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants