fix(membership): surface emergency-contact rejection as a 400, not a 500#288
Open
dkaygithub wants to merge 3 commits into
Open
fix(membership): surface emergency-contact rejection as a 400, not a 500#288dkaygithub wants to merge 3 commits into
dkaygithub wants to merge 3 commits into
Conversation
Starting/submitting a membership application could fail with a bare "Internal Server Error" and no indication of what was wrong. This makes the intake errors transparent: - Field-level validation: submit() highlights missing required inputs (home address, emergency contact name/phone, primary name) in red with inline messages before any network call — instant feedback on every environment, prod included. - Server-driven highlighting: IntakeError now carries `fields`, and submitIntake tags each missing requirement. The submit route returns those keys so the client can highlight cases it can't detect locally (e.g. an emergency contact who is a household member). - Unexpected 500s: error->response mapping is centralized in intakeResponse.ts and shared by all three membership routes. The real error message is echoed as `detail` only on dev/local instances (prod stays generic — no DB/stack leak); the full error is always logged. Existing intake integration test still holds (asserts 400 + code 'incomplete', both preserved; `fields` is additive). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve conflicts in the membership intake flow: - IntakeError: keep the new fields? param and add main's lead_limit code - intakeResponse.ts STATUS_FOR: add lead_limit so the shared map stays exhaustive - intake/submit/route + membership/route: keep the shared intakeErrorResponse helper, dropping main's now-redundant inline STATUS_FOR/handleError - membership/page.tsx: keep both fieldErrors and warnings state; combine apiError()/mapServerFields() error handling with main's saveWarnings flow; drop a duplicate saveRes.json() read Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
saveIntake -> upsertPrimaryContact throws EmergencyContactError when the intake's emergency contact matches a household member (shared name, email, or phone). The intake routes' shared intakeErrorResponse only translated IntakeError, so EmergencyContactError fell through to a generic 500 Internal Server Error — the message only surfaced at all because dev instances echo `detail`. Map EmergencyContactError to a 400 carrying its code and fields: ["emergencyContact"], matching how every other emergency-contact route already handles it. Client: the submit flow's PATCH-failure branch now highlights the returned fields, like its submit-POST branch already did. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Problem
Submitting the membership intake while the emergency contact collides with a household member (shared name, email, or phone) returned a raw 500 Internal Server Error instead of a clean, field-highlighted validation message.
Found while testing the join flow: a household of one (the applicant) with an emergency contact that reused the same mock phone number as the applicant. The not-a-household-member check (
emergencyContacts/identity.ts) matches on any one of phone/email/name, so it correctly decided the contact was the applicant — but the rejection surfaced as a 500.Root cause
saveIntake → upsertPrimaryContactthrows anEmergencyContactError. The intake routes' sharedintakeErrorResponseonly translatedIntakeError, soEmergencyContactErrorfell through to the generic 500 branch. (The message text only appeared because dev instances echodetail; prod would have shown a blank "Internal Server Error".) Every other emergency-contact route already catches this class and returns a 400 — the intake route was the only gap.Fix
lib/membership/intakeResponse.ts— mapEmergencyContactErrorto a400carrying itscodeandfields: ["emergencyContact"], consistent with the other routes and with the form's field-highlighting contract.app/membership/page.tsx— the submit flow's PATCH-failure branch now highlights returnedfields(viamapServerFields), matching what its submit-POST branch already did.Result: a clean inline error ("…can't be its emergency contact…") with the field highlighted, on prod as well as dev — no 500.
Behavior unchanged
The identity-matching rule itself is intentional (phone OR email OR name) and is left as-is. This PR is purely about error transparency.
Testing
tsc --noEmitcleantest:ci(mocked): 177 passed🤖 Generated with Claude Code