feat(kyc): mandatory EEA-uplift gate + funnel analytics#2309
Conversation
…lytics Users were skipping the EEA-uplift KYC via the modal's "Not now" / close and then continuing to transact, so the requirement never got completed before a bank transfer. Make the pre-empt mandatory: the modal is non-closable and non-skippable (preventClose + hideModalCloseButton, "Not now" removed), and the hook blocks the deferred deposit/withdraw action until the requirement clears. Only "Complete now" moves forward. Add dedicated PostHog events (eea_uplift_started / eea_uplift_completed, with channel + requirement_key) so the start->finish funnel can be filtered directly. Completion is ref-guarded so generic KYC successes on the bank pages don't mis-fire it. Scope: the advisory hard-gate path on add-money/[country]/bank and withdraw/[country]/bank. Front-end enforcement only — does not change Bridge's underlying requirement.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds EEA uplift analytics, a KYC approval callback, and a mandatory advisory gate, then wires the tracking and gate behavior into the deposit and withdrawal bank pages. ChangesEEA Uplift Funnel Tracking and Mandatory Advisory Gate
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Code-analysis diffPainscore total: 5845.66 → 5852.23 (+6.57) 🆕 New findings (45)
…and 25 more. ✅ Resolved (44)
…and 24 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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)/add-money/[country]/bank/page.tsx:
- Around line 86-91: The `eea_uplift_completed` event is being sent too late via
`useMultiPhaseKycFlow`’s `onKycSuccess`, which only runs after `completeFlow()`
finishes. Move the tracking to the Sumsub approval transition itself in
`bank/page.tsx` (or add a dedicated approved callback on `useMultiPhaseKycFlow`)
so `trackUpliftCompleted` fires when approval is reached, not when the full
post-approval flow ends. Keep the `setUrlState({ step: 'inputAmount' })`
navigation tied to the same approved path if appropriate.
In `@src/app/`(mobile-ui)/withdraw/[country]/bank/page.tsx:
- Around line 95-97: The `eea_uplift_completed` tracking is currently wired to
`useMultiPhaseKycFlow`’s `onKycSuccess`, which only fires at `completeFlow()`
and can miss approved uplift sessions if the user stalls afterward. Move the
`trackUpliftCompleted` emission to the approval transition in the withdrawal
bank flow (around `useEeaUpliftFunnel('withdraw')` and `useMultiPhaseKycFlow`)
so it triggers when uplift approval happens, not when the overall flow fully
settles.
In `@src/hooks/useAdvisoryPreempt.ts`:
- Around line 24-61: The modal visibility state in useAdvisoryPreempt is not
synchronized with the advisory lifecycle: completeNow currently hides the modal
before onCompleteNow finishes, and the hook never clears visible when advisory
becomes undefined. Update the hook so visible is driven by the advisory state in
useAdvisoryPreempt, keeping the modal open until the self-heal request resolves
successfully and automatically closing it when advisory is removed; use the
existing intercept, completeNow, and modalProps wiring to ensure the hard gate
does not disappear on failure or linger after clearance.
In `@src/hooks/useEeaUpliftFunnel.ts`:
- Around line 22-30: `trackStarted` in `useEeaUpliftFunnel` should not fire
`EEA_UPLIFT_STARTED` without a real `GateAdvisory`. Make the `advisory`
parameter required (or return early when it is undefined) so
`startedRef.current` is always set from a valid advisory and `posthog.capture`
always sends `requirement_key`, `action_key`, and `effective_date` together with
`channel`.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7e0c777e-b0cb-44bc-950d-519f55cfa9d7
📒 Files selected for processing (8)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/components/Kyc/AdvisoryPreemptModal.tsxsrc/constants/analytics.consts.tssrc/hooks/useAdvisoryPreempt.test.tssrc/hooks/useAdvisoryPreempt.tssrc/hooks/useEeaUpliftFunnel.test.tssrc/hooks/useEeaUpliftFunnel.ts
The startedRef latch was only cleared on success, so an abandoned uplift attempt could leave it set and a later unrelated KYC success on the same mounted page would mis-fire eea_uplift_completed (inflating the funnel). Reset the pending start via the KYC flow's onManualClose (abandon) on both bank pages. Covers the main mis-attribution path surfaced in review.
…dal lifecycle CodeRabbit + review findings: - Move eea_uplift_completed off the end-of-flow onKycSuccess onto a new onKycApproved callback fired at the Sumsub approval transition, so it isn't lost when the user drops during post-approval ToS/preparing (undercount) and isn't fired on a ToS-skip without finishing (overcount). - useAdvisoryPreempt: auto-close the modal when the advisory clears while open, and re-show it if the launch fails — the hard gate must not silently vanish or linger over an already-cleared requirement. - useEeaUpliftFunnel: require a real advisory for trackStarted (no partial started payloads).
|
@coderabbitai resolve |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (1)
144-148: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winClear the uplift latch when the self-heal launch fails.
Lines 146-147 mark the funnel as started before
handleSelfHealResubmit()resolves. If that launch rejects,useAdvisoryPreemptjust re-shows the gate, but the stored advisory remains latched, so a later unrelatedonKycApprovedon this page can still emiteea_uplift_completedfor a failed attempt.Suggested fix
- onCompleteNow: () => { - if (!advisory) return Promise.resolve() - trackUpliftStarted(advisory) - return sumsubFlow.handleSelfHealResubmit('BRIDGE', advisory.requirementKey) - }, + onCompleteNow: async () => { + if (!advisory) return + trackUpliftStarted(advisory) + try { + await sumsubFlow.handleSelfHealResubmit('BRIDGE', advisory.requirementKey) + } catch (error) { + resetUpliftFunnel() + throw error + } + },🤖 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)/add-money/[country]/bank/page.tsx around lines 144 - 148, The uplift latch is being set in onCompleteNow before handleSelfHealResubmit() succeeds, so a failed self-heal can leave advisory state stuck and later trigger a false eea_uplift_completed. Update the onCompleteNow flow in the bank page to clear or reset the stored advisory/latch when sumsubFlow.handleSelfHealResubmit('BRIDGE', advisory.requirementKey) rejects, and make sure the failure path in useAdvisoryPreempt does not leave the tracked advisory latched after re-showing the gate.
🤖 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/hooks/useMultiPhaseKycFlow.ts`:
- Around line 179-183: The new onKycApproved callback in useMultiPhaseKycFlow
should not be able to stop the approval state machine from continuing. Wrap the
onKycApproved?.() call in a best-effort failure boundary so any thrown error is
isolated and the flow still proceeds to fetchUser() and the existing
post-approval branching for ToS, preparing, and complete states. Keep the change
localized around the approval handling path in useMultiPhaseKycFlow so the
callback remains optional side-effect work only.
---
Outside diff comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 144-148: The uplift latch is being set in onCompleteNow before
handleSelfHealResubmit() succeeds, so a failed self-heal can leave advisory
state stuck and later trigger a false eea_uplift_completed. Update the
onCompleteNow flow in the bank page to clear or reset the stored advisory/latch
when sumsubFlow.handleSelfHealResubmit('BRIDGE', advisory.requirementKey)
rejects, and make sure the failure path in useAdvisoryPreempt does not leave the
tracked advisory latched after re-showing the gate.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5b86215d-15e2-485f-8bd8-8c537303cd9a
📒 Files selected for processing (6)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/hooks/useAdvisoryPreempt.test.tssrc/hooks/useAdvisoryPreempt.tssrc/hooks/useEeaUpliftFunnel.tssrc/hooks/useMultiPhaseKycFlow.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/hooks/useEeaUpliftFunnel.ts
- src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
- src/hooks/useAdvisoryPreempt.ts
✅ Action performedComments resolved. Approval is disabled; enable |
|
🚑 Retargeted to Back-merge note: Status: CI required checks green · CodeRabbit ✅ (4 findings resolved) · eslint 🟡 known-baseline (not worsened) · |
Summary
Users were skipping the EEA-uplift KYC via the modal's "Not now" / close button and then continuing to transact — so the Bridge endorsement requirement never got completed before a bank transfer. This makes the verification a hard gate:
AdvisoryPreemptModalis now non-closable + non-skippable (preventClose+hideModalCloseButton, "Not now" removed). Only "Complete now" proceeds.useAdvisoryPreemptblocks the deferred deposit/withdraw action while a requirement is pending; it passes through only once the gate clears theadvisory. It also auto-closes if the requirement clears while open, and re-shows if the launch fails.Analytics (the second ask)
Two dedicated PostHog events so the funnel can be filtered directly:
eea_uplift_started— fired when the user taps "Complete now".eea_uplift_completed— fired at the Sumsub approval transition (verification submitted), via a newonKycApprovedcallback onuseMultiPhaseKycFlow— not at end-of-flow, so it isn't lost if the user drops during post-approval ToS/preparing, and isn't mis-fired on a ToS-skip. Ref-guarded + reset on abandon so non-uplift KYC successes don't mis-fire it.channel(deposit/withdraw),requirement_key,action_key,effective_date.Risks / scope
add-money/[country]/bankandwithdraw/[country]/bank. The post-cliff blockingInitiateKycModalpath still emits the generickyc_*events; the dedicated events are not wired there.onKycApprovedis additive on the shareduseMultiPhaseKycFlow(optional, no-op for existing consumers).QA
useAdvisoryPreempt.test.ts+useEeaUpliftFunnel.test.ts— 13 tests (hard-gate semantics, modal lifecycle, funnel firing/guards/reset).eea_upliftrequirement, open add-money/[country]/bank → tap continue → modal appears with no X / no backdrop dismiss / no "Not now"; "Complete now" launches Sumsub;eea_uplift_startedfires on launch,eea_uplift_completedon Sumsub approval. Same on withdraw/[country]/bank.Local gate: prettier ✅ · typecheck ✅ · unit 13/13 ✅. (eslint is the repo's known-amber baseline — not worsened; new code lints clean.)