Skip to content

fix(card): prioritize card CTA for eligible users over region-picker KYC#2262

Merged
Hugo0 merged 3 commits into
mainfrom
feat/card-cta-priority
Jun 19, 2026
Merged

fix(card): prioritize card CTA for eligible users over region-picker KYC#2262
Hugo0 merged 3 commits into
mainfrom
feat/card-cta-priority

Conversation

@jjramirezn

Copy link
Copy Markdown
Contributor

Summary

Card-eligible users were being routed to the region picker before ever seeing the card. The home activation funnel only inserted the card step after the user was KYC-approved and funded, and the carousel kyc-prompt always pointed at /profile/identity-verification.

For EU/NA users that picker maps the region intent → bridge-requirements and auto-enrolls Bridge bank rails — whose database checks fail on many EU (e.g. Polish) addresses, surfacing as a "blocked by Bridge / proof of address" dead-end for people who only ever wanted a card.

This makes the card CTA take priority for eligible users and routes them to /card, whose KYC runs on the rain-requirements Sumsub level (no regionIntent, no Bridge rail enrollment).

  • useActivationStatus: the card step now wins over verify/deposit/outbound (and fires for activated-but-cardless users), gated on hasCardAccess && !hasCard && !dismissed.
  • useHomeCarouselCTAs: suppress the region-picker kyc-prompt for users who already have card access.

"Eligible" = cardInfo.hasCardAccess — a skip badge (e.g. WAITLIST_SKIP) or an admin grant. This is already phase-aware on the backend (activeSkipBadgeCodes()), so the gate widens automatically at public launch with no FE change.

Risk

Low / FE-only. Changes which single home CTA shows; all routing targets already exist. Eligible users now see "Get your card" first instead of "Unlock payments"; non-eligible users are unchanged. No API/contract change.

⚠️ Base is main per request. The repo convention for features is dev — flagging in case this should retarget.

QA (sandbox)

  • Eligible user (skip badge / grant), no card → home shows Get your card/card (rain-requirements KYC, no region picker).
  • Eligible user taps Maybe later → falls back to the normal funnel.
  • User with an active card → no card CTA.
  • Non-eligible user → unchanged: sees verify / kyc-prompt → region picker.

Card-eligible users (skip badge or admin grant — both collapse into
cardInfo.hasCardAccess on the BE) were funneled to the region picker first:
the home activation funnel only surfaced the card step after KYC + funding,
and the carousel KYC prompt always pointed at /profile/identity-verification.

For EU/NA users that picker maps the region intent to bridge-requirements and
auto-enrolls Bridge bank rails, whose DB checks fail on many EU addresses —
surfacing as a "blocked by Bridge / proof of address" detour for people who
only ever wanted a card.

Surface the card step first whenever the user is eligible and cardless,
routing them to /card (KYC runs on rain-requirements — no regionIntent, no
Bridge rails), and stop showing the region-picker carousel CTA to eligible
users.
@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jun 19, 2026 8:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4649ca76-7d41-46a7-a55b-9e2e5f381576

📥 Commits

Reviewing files that changed from the base of the PR and between 7853439 and d3fb0fa.

📒 Files selected for processing (1)
  • src/components/Profile/views/UnlockedRegions.view.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Profile/views/UnlockedRegions.view.tsx

Walkthrough

Card access state is elevated to a first-class priority across activation flow and region KYC initiation. Two hooks now gate on card-eligible status: useActivationStatus unconditionally prioritizes the card step when card access is granted and no active card exists, and useHomeCarouselCTAs suppresses the kyc-prompt CTA for card-eligible users. The region view adds a redirect guard that sends card-eligible users lacking an active card to /card instead of initiating region KYC.

Changes

Card Access Priority Across Activation Flow and Region KYC

Layer / File(s) Summary
Card step unconditional priority in activation and KYC CTA suppression
src/hooks/useActivationStatus.ts, src/hooks/useHomeCarouselCTAs.tsx
useActivationStatus replaces the narrow "only when outbound" rule with an unconditional priority check that sets activationStep to 'card' when card access is granted, no active card exists, and the dismissal flag is false. useHomeCarouselCTAs tightens the kyc-prompt eligibility check to additionally require hasCardAccessGranted === false, suppressing the CTA for card-eligible users.
Region KYC redirect guard for card-eligible users
src/components/Profile/views/UnlockedRegions.view.tsx
Adds imports for routing and card-status derivation. Derives hasCardAccess and a tri-state hasActiveCard to avoid redirecting before the card overview finishes loading. In handleStartKyc, a redirect guard checks if the user has card access but no active card; if true, clears the selected region and redirects to /card, bypassing region KYC initiation. Dependency list updated to include router and card state values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • peanutprotocol/peanut-ui#1989: Both PRs modify src/hooks/useActivationStatus.ts's activationStep derivation logic, changing the precedence and override conditions for step prioritization including the card step.
  • peanutprotocol/peanut-ui#2063: Both PRs modify src/hooks/useHomeCarouselCTAs.tsx to conditionally suppress the kyc-prompt CTA based on card or identity verification status.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: prioritizing card CTA for eligible users over region-picker KYC, which aligns with the core objective across all three modified files.
Description check ✅ Passed The description comprehensively explains the problem, solution, affected components, risk assessment, and QA scenarios, directly relating to the changeset modifications in useActivationStatus, useHomeCarouselCTAs, and UnlockedRegions.view.tsx.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5732.06 → 5733.06 (+1)
Findings: 0 net (+15 new, -15 resolved)

🆕 New findings (15)

  • critical complexity — src/components/Profile/views/UnlockedRegions.view.tsx — CC 66, MI 61.73, SLOC 198
  • critical complexity — src/hooks/useHomeCarouselCTAs.tsx — CC 54, MI 59.49, SLOC 235
  • high hotspot — src/hooks/useHomeCarouselCTAs.tsx — 32 commits, +367/-225 lines since 6 months ago
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:72 — useHomeCarouselCTAs: MDD 145.5 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:132 — : MDD 93.2 (uses across many lines from declarations)
  • medium high-mdd — src/components/Profile/views/UnlockedRegions.view.tsx:64 — UnlockedRegions: MDD 90.1 (uses across many lines from declarations)
  • medium high-dlt — src/components/Profile/views/UnlockedRegions.view.tsx:64 — UnlockedRegions: DLT 32 (calls 32 distinct functions — high context load)
  • medium method-complexity — src/components/Profile/views/UnlockedRegions.view.tsx:64 — CC 26 SLOC 123
  • medium method-complexity — src/hooks/useHomeCarouselCTAs.tsx:132 — CC 24 SLOC 88
  • medium high-mdd — src/hooks/useActivationStatus.ts:42 — useActivationStatus: MDD 22.4 (uses across many lines from declarations)
  • medium complexity — src/hooks/useActivationStatus.ts — CC 19, MI 56.27, SLOC 81
  • low high-mdd — src/hooks/useActivationStatus.ts:72 — : MDD 18.1 (uses across many lines from declarations)
  • low structural-dup — components/Profile/views/UnlockedRegions.view.tsx:374 — 16 duplicate lines / 83 tokens with features/limits/views/LimitsPageView.tsx:146
  • low structural-dup — components/Profile/views/UnlockedRegions.view.tsx:375 — 16 duplicate lines / 73 tokens with features/limits/views/LimitsPageView.tsx:189
  • low high-mdd — src/components/Profile/views/UnlockedRegions.view.tsx:375 — : MDD 10.6 (uses across many lines from declarations)

✅ Resolved (15)

  • src/components/Profile/views/UnlockedRegions.view.tsx — CC 62, MI 62.4, SLOC 179
  • src/hooks/useHomeCarouselCTAs.tsx — CC 53, MI 59.52, SLOC 235
  • src/hooks/useHomeCarouselCTAs.tsx — 31 commits, +360/-224 lines since 6 months ago
  • src/hooks/useHomeCarouselCTAs.tsx:72 — useHomeCarouselCTAs: MDD 143.0 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:132 — : MDD 91.9 (uses across many lines from declarations)
  • src/components/Profile/views/UnlockedRegions.view.tsx:60 — UnlockedRegions: MDD 82.0 (uses across many lines from declarations)
  • src/components/Profile/views/UnlockedRegions.view.tsx:60 — CC 26 SLOC 112
  • src/hooks/useHomeCarouselCTAs.tsx:132 — CC 23 SLOC 88
  • src/hooks/useActivationStatus.ts — CC 20, MI 56.07, SLOC 82
  • src/components/Profile/views/UnlockedRegions.view.tsx:60 — UnlockedRegions: DLT 27 (calls 27 distinct functions — high context load)
  • src/hooks/useActivationStatus.ts:42 — useActivationStatus: MDD 18.7 (uses across many lines from declarations)
  • components/Profile/views/UnlockedRegions.view.tsx:347 — 16 duplicate lines / 83 tokens with features/limits/views/LimitsPageView.tsx:146
  • components/Profile/views/UnlockedRegions.view.tsx:348 — 16 duplicate lines / 73 tokens with features/limits/views/LimitsPageView.tsx:189
  • src/hooks/useActivationStatus.ts:72 — : MDD 15.3 (uses across many lines from declarations)
  • src/components/Profile/views/UnlockedRegions.view.tsx:348 — : MDD 10.6 (uses across many lines from declarations)

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Profile/views/UnlockedRegions.view.tsx 8.9 9.4 +0.6

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1468 ran, 0 failed, 0 skipped, 21.9s

📊 Coverage (unit)

metric %
statements 52.4%
branches 34.8%
functions 39.6%
lines 52.3%
⏱ 10 slowest test cases
time test
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.4s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every stamp stays within canvas at any count
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
0.1s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Manteca PIX form ready shows merchant card + amount input + pay button
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid UK IBAN with spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (missing 0x prefix)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (invalid characters)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid US account with spaces
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jun 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/useHomeCarouselCTAs.tsx`:
- Around line 298-304: The useCardInfo() and useActivationStatus hooks are
constructing their query keys inconsistently. Locate both hooks and examine
their query key construction for the 'card-info' key. The useCardInfo hook uses
['card-info', user?.user?.userId] while useActivationStatus uses ['card-info',
userId]. Standardize the query key parameters so both hooks extract and
reference userId in the same way (e.g., both should use the same pattern like
userId or both should use user?.user?.userId) to ensure React Query treats them
as a single cache entry and maintains consistent state across all consumers.
🪄 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: 4e97d32c-7068-42a1-9d5b-c00d3da3df9b

📥 Commits

Reviewing files that changed from the base of the PR and between 9343a8f and 3af8fd6.

📒 Files selected for processing (2)
  • src/hooks/useActivationStatus.ts
  • src/hooks/useHomeCarouselCTAs.tsx

Comment thread src/hooks/useHomeCarouselCTAs.tsx
@jjramirezn jjramirezn marked this pull request as ready for review June 19, 2026 19:36
Extends the card-priority behavior to the region picker. When a user has
card access (skip badge / admin grant -> cardInfo.hasCardAccess) but no
active card, handleStartKyc now sends them to /card — whose KYC runs on the
rain-requirements Sumsub level (no regionIntent, no Bridge/Manteca rail
enrollment) — instead of starting region KYC, regardless of which region
they selected. Card-holders fall through and can still unlock bank regions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/components/Profile/views/UnlockedRegions.view.tsx`:
- Around line 72-74: The hasActiveCard computation on line 73 uses the
double-bang operator (!!findActiveCard(overview)) which converts undefined to
false, treating the unknown/loading state the same as "no active card". This
causes premature redirects to /card before the card overview finishes loading.
Remove the !! operator from the findActiveCard(overview) call in the useMemo
hook to preserve the tri-state value (true | false | undefined), and then update
the redirect logic at line 172-175 to only redirect when hasActiveCard is
explicitly false, not when it is undefined.
🪄 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: b140af0f-697b-463b-a087-910580458191

📥 Commits

Reviewing files that changed from the base of the PR and between 3af8fd6 and 7853439.

📒 Files selected for processing (1)
  • src/components/Profile/views/UnlockedRegions.view.tsx

Comment thread src/components/Profile/views/UnlockedRegions.view.tsx
Address CodeRabbit (Major): while useRainCardOverview is still loading,
treating `overview` as "no active card" could bounce a card-holder to /card
before it resolves. Make hasActiveCard tri-state (undefined while loading)
and only redirect on an explicit `false`.
@Hugo0 Hugo0 merged commit 0524b01 into main Jun 19, 2026
22 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants