feat: Add/Edit Family tool + Google Places provider#10
Merged
Conversation
Adds a reusable Google Places integration following the existing
provider/service pattern (mirrors MP). All calls run on the server so
the API key never leaves the host.
Provider (src/lib/providers/google-places/)
- GooglePlacesProvider class wrapping the Places API v1 REST endpoints
(places:autocomplete, places/{id})
- Uses session tokens per autocomplete -> details cycle so Google bills
a single suggestion-session instead of per-keystroke
- X-Goog-FieldMask trims response payloads to only what the client uses
Service (src/services/googlePlacesService.ts)
- Singleton matching the existing service pattern
- isEnabled() returns true only when GOOGLE_PLACES_API_KEY is set; the
consumer should fall back gracefully when missing
- Lazy provider instantiation on first call
Config
- Adds GOOGLE_PLACES_API_KEY to .env.example with notes on which API
to enable (Places API New / v1) and that keys should be restricted
by IP/service rather than HTTP referrer since calls are server-side
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A new tool for creating and editing MP households end-to-end: search
existing families, edit household + members + addresses, or start fresh
from the search bar. Modeled on MP's own cloud Add/Edit Family but built
on this project's MP REST + service pattern.
DTOs (src/lib/dto/family.ts)
- Household, FamilyMember, FamilyAddress + Zod schemas
- SaveProgress / SavedMemberId for partial-failure tracking
- emptyHousehold/emptyMember factories with defaults seeded
Service (src/services/familyService.ts)
- Singleton matching the existing service pattern
- searchContacts: typeahead against Contacts joined to household address
- getHousehold: single round-trip with FK-joined main + alt address
columns and member rows (Participant_Record + Donor_Record joined for
Participant_Type_ID and Envelope_No)
- resolveContactIdFromPage: honors ToolParams.pageData.Contact_ID_Field
so the tool works from any MP page whose metadata exposes the path
back to a Contact_ID (donors, participants, custom pages, etc.)
- getLookups: parallel fetch of every dropdown (congregations, sources,
household positions, participant types, marital statuses, prefixes,
suffixes, genders, contact statuses, primary languages, faith
backgrounds, countries) + a hard-coded US states list (MP has no
States table; MP's cloud tool also ships its own)
- getNextEnvelopeNumber: MAX(Envelope_No) + 1 against Donors
- saveHousehold: progress-aware multi-step upsert with race-hardened
envelope assignment. Throws PartialSaveError carrying the progress
so the UI can patch local state with real IDs even on partial failure
-- retries don't duplicate
Server actions (src/app/(web)/tools/addeditfamily/actions.ts)
- Thin wrappers around the service with auth gating and unified
ActionError shape that includes optional progress for the partial-
failure path
- placesEnabled/placeAutocomplete/placeDetails for Google Places
UI (src/app/(web)/tools/addeditfamily/add-edit-family.tsx)
- Single screen: search bar, household panel, address tabs (main/alt
with season fields), member cards (compact + More toggle), Add Member
button, Save/Close in ToolContainer footer
- Donor toggle locks (with tooltip) when a Donor record exists; envelope
# appears when Donor is on, with an "Assign Next Envelope #" button
that fetches MAX+1 from the server
- AddressLine1Autocomplete (Google Places) auto-enables when the key
is configured server-side; falls back to a plain Input otherwise
- Dirty-check on Close: snapshot the household on load/save, compare
via JSON equality, show an AlertDialog before discarding
- Save flow: applies progress (real contact/participant/donor/address
IDs replace temp negatives) on both success and partial failure;
re-loads from MP after success to show the normalized server view;
surface errors via sonner toast. Envelope bumps from the race-
hardening loop emit a separate warning toast naming the new number
Page wiring (src/app/(web)/tools/addeditfamily/page.tsx)
- Resolves recordID -> Contact_ID server-side using pageData metadata
so launching the tool from a non-Households page lands on the right
family without a round-trip
Homepage
- Adds an Add/Edit Family card to src/app/(web)/page.tsx
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Summary
/tools/addeditfamilymodeled on MP's cloud Add/Edit Family but built on this project's MP REST + service pattern. Search, edit, or create households end-to-end (household + main/alt addresses + members + donor + envelope).What landed
Tool (commit
281c7ee)FamilyServicesingleton, server actions, full UIrecordIDresolution honorspageData.Contact_ID_Fieldso the tool works from any MP page whose metadata exposes the path back to a Contact_IDDonors.Envelope_Nouniqueness and bumps toMAX+1if taken (up to 5 attempts); the bumped number is surfaced back to the UI via a warning toastAlertDialog; save → reload + sonner toast; no navigation away after saveGoogle Places provider (commit
0dba1d5)GooglePlacesProviderwraps Places API v1 REST endpoints with session-token billingGooglePlacesServicesingleton,.isEnabled()for graceful degradation.env.examplenotes the key, which API to enable, and the IP/service restriction guidance (calls are server-side, not referrer-bound)Test plan
GOOGLE_PLACES_API_KEYto.env.local, restart dev server → Address Line 1 becomes a typeahead; selecting a result populates Line 1/City/State/Zip/Country🤖 Generated with Claude Code