feat(contacts): shared ContactForm, ContactAvatar, form hooks [PERA-3932]#373
Merged
feat(contacts): shared ContactForm, ContactAvatar, form hooks [PERA-3932]#373
Conversation
- 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.
5 tasks
wjbeau
approved these changes
Apr 24, 2026
… 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.
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").
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
In-module commons shared by add + edit + view flows:
Related Issues
Checklist
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.