feat(card): proof-of-address upload on stuck Rain applications#2438
feat(card): proof-of-address upload on stuck Rain applications#2438jjramirezn wants to merge 5 commits into
Conversation
The card status screens for requires-info/rejected only offered "Contact support", even when the backend classified the application as PoA-fixable (residence vs POI country mismatch, the digital-nomad case that drives most manual card support). When the rain rail carries the sumsub:proof_of_address next-action, the status screen now renders a primary "Upload proof of address" CTA that opens the Sumsub RFI flow via the self-heal machinery (provider RAIN on /users/identity/resubmit — peanut-api-ts#1194 is the backend half). On completion the rain overview refetches and the backend flips the rail reason to "document received - being reviewed". The self-heal flow gets its own SumsubKycModals surface — the existing SumsubKycWrapper on this page is bound to card-apply tokens with their own refresh/poll semantics and must not be multiplexed.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe card flow adds a Rain proof-of-address self-heal upload action, extends Sumsub resubmission types, updates status-screen CTAs and messaging, and renders a dedicated Sumsub upload wrapper with completion refresh behavior. ChangesRain proof-of-address remediation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ApplicationStatusScreen
participant CardPage
participant initiateSelfHealResubmission
participant SumsubKycWrapper
ApplicationStatusScreen->>CardPage: invoke onUploadProofOfAddress
CardPage->>initiateSelfHealResubmission: request Rain resubmission token
initiateSelfHealResubmission-->>CardPage: return upload token
CardPage->>SumsubKycWrapper: render PoA upload flow with token
SumsubKycWrapper-->>CardPage: complete upload
CardPage->>CardPage: invalidate Rain overview and refresh user
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code-analysis diffPainscore total: 6207.44 → 6210.18 (+2.74) 🆕 New findings (14)
✅ Resolved (13)
📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/app/(mobile-ui)/card/page.tsx (1)
217-240: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider distinguishing self-heal from card-application in analytics.
CARD_SUMSUB_OPENEDis fired here for the self-heal PoA upload, but the same event is used at lines 254, 296, and 322 for the card-application Sumsub flow. Without a distinguishing property (e.g.flow: 'self-heal'vs'card-application'), funnel analysis can't tell whether the user entered Sumsub to fix a PoA issue or as part of the initial card application.📊 Suggested analytics distinction
const onUploadProofOfAddress = poaAction ? () => { - posthog.capture(ANALYTICS_EVENTS.CARD_SUMSUB_OPENED) + posthog.capture(ANALYTICS_EVENTS.CARD_SUMSUB_OPENED, { flow: 'self-heal' }) void selfHealKycFlow.handleSelfHealResubmit('RAIN') } : undefined🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`(mobile-ui)/card/page.tsx around lines 217 - 240, Update the CARD_SUMSUB_OPENED capture in onUploadProofOfAddress to include a distinguishing flow property such as self-heal, while preserving the existing event and payload used by the card-application Sumsub flow elsewhere in the page.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/app/`(mobile-ui)/card/page.tsx:
- Around line 217-240: Update the CARD_SUMSUB_OPENED capture in
onUploadProofOfAddress to include a distinguishing flow property such as
self-heal, while preserving the existing event and payload used by the
card-application Sumsub flow elsewhere in the page.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 05678352-9bec-442c-a993-f0bcf5be746a
📒 Files selected for processing (5)
src/app/(mobile-ui)/card/page.tsxsrc/app/actions/sumsub.tssrc/components/Card/ApplicationStatusScreen.tsxsrc/components/Card/__tests__/ApplicationStatusScreen.test.tsxsrc/hooks/useSumsubKycFlow.ts
…urce Align SelfHealResubmissionResponse.requiredAction with the backend's RAIN_DOCUMENT value, and tag the self-heal CARD_SUMSUB_OPENED capture with source: poa-self-heal so it's distinguishable from card-apply.
|
CodeRabbit nitpick addressed in 880c28d: the self-heal capture now carries |
Review found the useMultiPhaseKycFlow reuse wrong for this surface: its post-approval phase machine is bank-onboarding shaped (mutating status poll that can hang the 'verifying' modal indefinitely, Bridge ToS fan-out on an unrelated rail, completeFlow re-rendering the same requires-info screen so users re-upload and burn attempts), and initiation errors were invisible - a dead primary CTA on a stuck- application screen. Replaced with a tiny dedicated flow: call the resubmit action directly, drive a second SumsubKycWrapper with its own token state, render initiation failures inline under the CTA, and flip an optimistic 'we received your document' banner on SDK completion until the backend webhook stamps the rail. The useSumsubKycFlow union widening is reverted - no longer needed.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(mobile-ui)/card/page.tsx:
- Around line 240-250: Update startPoaUpload to guard against concurrent
execution by tracking an in-flight request and returning immediately when one is
already active; set and clear this guard across all success and error paths.
Also disable the PoA resubmission CTA while the request is in flight,
re-enabling it after completion.
- Around line 715-718: Update the refresh flow around fetchUser() to reset the
local poaSubmitted state after the refreshed user capabilities no longer require
the optimistic PoA banner. Preserve the existing setPoaToken(null),
invalidateOverview(), and fetchUser() behavior while ensuring the upload CTA can
reappear without requiring a remount.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5b37128b-8bbc-4b42-964a-c0d7ee3c001d
📒 Files selected for processing (4)
src/app/(mobile-ui)/card/page.tsxsrc/app/actions/sumsub.tssrc/components/Card/ApplicationStatusScreen.tsxsrc/components/Card/__tests__/ApplicationStatusScreen.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/Card/tests/ApplicationStatusScreen.test.tsx
- src/components/Card/ApplicationStatusScreen.tsx
Concurrent CTA clicks could mint two Sumsub actions (the backend id collision path deliberately creates a suffixed fresh action); guard with a ref. Reset poaSubmitted once the backend state takes over so a re-offered upload isn't suppressed until remount.
Hotfix port to main of peanut-ui#2438 (branch was dev-based; every touched file is byte-identical between dev and main, so this is the same reviewed content squashed onto main). Pairs with peanut-api-ts hotfix/rain-poa-selfheal — deploy the API first. When the rain rail carries the sumsub:proof_of_address next-action, the card status screens (requires-info / rejected) render a primary "Upload proof of address" CTA driving a dedicated lightweight flow: resubmit action -> second SumsubKycWrapper -> inline initiation errors -> optimistic "we received your document" banner (auto-reset when the backend state takes over). Double-click guard; analytics tagged source: poa-self-heal. Full review trail on #2438.
|
Superseded by #2446 — same content retargeted to main as a hotfix per Jota. Review trail stays here. |
Summary
FE half of the Rain proof-of-address self-heal (backend: peanutprotocol/peanut-api-ts#1194). Users whose card application is stuck on an address/PoA mismatch currently dead-end at "Contact support" on the card status screen; this PR gives them a primary "Upload proof of address" CTA that opens the Sumsub RFI upload in-app.
ApplicationStatusScreen: new optionalonUploadProofOfAddressprop → primary purple CTA above the existing Contact-support link (requires-info / requires-support / rejected variants only).card/page.tsx: derives the CTA from the capabilities read-model — shown only when the rain rail carries asumsubnext-action withlevelKey: 'proof_of_address'(what the backend emits forWRONG_ADDRESS-class denials). Drives a dedicated, deliberately tiny flow:initiateSelfHealResubmission('RAIN')→ secondSumsubKycWrapperwith its own token state → on SDK completion, optimistic "we received your document" banner + overview/capabilities refetch. Initiation failures render inline under the CTA (uploadError).initiateSelfHealResubmission's provider param (+RAIN_DOCUMENTin the response union) —useSumsubKycFlowis untouched.Design notes / accepted trade-offs
useMultiPhaseKycFlow: review confirmed its post-approval phase machine is bank-onboarding-shaped — a mutating status poll that can hang the "verifying" modal indefinitely for already-approved applicants, possible Bridge-ToS modal fan-out on an unrelated rail, and a completeFlow that re-renders the same requires-info screen (users would re-upload and burn attempts). The PoA flow needs none of it: token → SDK → thank-you + refetch.SumsubKycWrapperis driven by card-apply tokens (applyForCard→ poll semantics); the PoA wrapper has its own token state — multiplexing them would fight over token refresh.poaSubmittedstate hides the CTA and shows "we received your document" until the backend's own wait-state reason takes over on refetch.rejectedtoo: Raindeniedwith a fixable PoA reason still carries the next-action (the backend'sresolvedverdict isfixable), and those users can be rescued without support.Risks / breaking changes
provider: 'RAIN'against the old backend they get a 400 "Invalid provider" toast.QA
npm test— newApplicationStatusScreencases: CTA renders + fires on requires-info and rejected, absent without the action, never on non-support variants. 4 failing suites on this branch (add-money-states,countryCurrencyMapping,GeneralRecipientInput,request-states) are unrelated to this diff — verifying against base-branch CI.REQUIRES_INFORMATIONwithrainRemediation.nextAction.documentPurpose === 'proof_of_address'(any of the 6 currently-stuck prod users' state reproduced in sandbox).createCommitOnBranch, verified web-flow signature) because local GPG signing was unavailable during this run.Summary by CodeRabbit
New Features
Bug Fixes