Skip to content

feat(kyc): dumb FE — useCapabilities + consumer migration#2122

Merged
Hugo0 merged 30 commits into
devfrom
feat/kyc-capabilities
May 29, 2026
Merged

feat(kyc): dumb FE — useCapabilities + consumer migration#2122
Hugo0 merged 30 commits into
devfrom
feat/kyc-capabilities

Conversation

@Hugo0

@Hugo0 Hugo0 commented May 27, 2026

Copy link
Copy Markdown
Contributor

DRAFT — frontend half of the KYC capability rehaul. Pairs with peanut-api-ts #897. Plan: engineering/projects/kyc-2.0/capabilities-rehaul-plan.md (mono).

What this does

Makes the FE provider-blind. It reads exactly two backend-computed read-models and renders them, never deriving KYC state and never knowing about providers:

  1. useCapabilities() (forward — what can you do) — rails / per-op refinement / nextActions / restrictions. Provider rejections surface as rail states + user-framed reasons + actions, not as "Bridge rejected you".
  2. useIdentityVerification() (backward — the one identity check the user did) — { status, actionMessage?, rejectLabels?, submittedAt?, reviewedAt? }, mirrored from Sumsub but provider-agnostic on the FE.

Plus a single counterparty signal for the guest claim-to-bank flow:

  1. CounterpartyUser.canReceiveBankOfframp: boolean — from getUserById. Replaces the raw bridgeKycStatus !== 'approved' check. The decision is "can we off-ramp through this sender", which IS a capability question.

Done

  • useCapabilities() — memoized selector + helpers (getRail, hasEnabledRail, operationStatus, canDo, getNextAction, …). Polls (4s) while any rail pending.
  • useIdentityVerification() — thin selector over user.identityVerification, mirroring the BE projection.
  • Derivation utils (faithful to the deleted hooks, with exhaustive case traces): bridge-gate.utils.ts, provider-rejection.utils.ts, regions.utils.ts.
  • Status surfaces rewritten on identityVerification (provider-blind): KycStatusItem/KycStatusDrawer switch on identity.status; state cards take generic submittedAt/reviewedAt/actionMessage/rejectLabels props. KycProviderRejection + KycRequiresDocuments + KycNotStarted deleted (no longer reachable in the provider-blind model). useMultiPhaseKycFlow + useSumsubKycFlow kept (SDK orchestration).
  • 7 legacy KYC state hooks DELETED (+ their tests): useUnifiedKycStatus, useKycStatus, useQrKycGate, useProviderRejectionStatus, useRailStatusTracking, useBridgeTransferReadiness, useIdentityVerification. ~1470 LoC net removed.
  • 6 raw KYC fields removed from IUserProfile.UserbridgeKycStatus, bridgeKyc{StartedAt,ApprovedAt,RejectedAt,RejectionReasonString}, kycVerifications. The current user is fully provider-blind. (Kept: IUserRail, IUserKycVerification type def, Contact.bridgeKycStatus — counterparty in a different shape.)
  • CounterpartyUser = User & { canReceiveBankOfframp: boolean } (no raw KYC). BankFlowManager guest branch + useDetermineBank{Claim,Request}Type migrated.
  • ClaimBankFlowContext.senderKycStatus dropped (orphaned context state, no consumers).

Tests + verification

  • FE Jest: 1157 pass, 60/61 suites (only the preexisting src/lib/content.test.ts content-submodule failure, identical to base — not ours).
  • tsc clean (only preexisting SEO/content-loader/.gif errors).
  • Provider-blindness grep: git grep -iE "bridgeKyc|kycVerifications" src/ — zero current-user reads; remaining hits are Contact.bridgeKycStatus, kept type defs, capability-derived variable names like isUserBridgeKycApproved = hasEnabledRail('bridge'), comments, and the legacy dev/debug whoami.
  • Live HTTP e2e against the running API (paired with Feat/request link preview #897 deployed locally) confirms the new shape: /get-user returns {capabilities, identityVerification}; /users/:userId returns canReceiveBankOfframp with bridgeKycStatus absent.

Open follow-up flags (not in this PR pair)

  • kyc.consts.isUserKycVerified is a separate concern — the "is verified" badge used by send/request/contacts/public-profile reads raw fields across different counterparty endpoints (Contact, ApiUser). Cleaning those is narrower + separable.
  • useCrispUserData.mantecaUserId degrades to undefined — was an internal support-dashboard convenience link from the removed user.kycVerifications.providerUserId. Not a gate, not user-facing.
  • getRegionAccess uses isKycApproved (any enabled rail ⇒ identity cleared at least once) as the Sumsub-approved proxy — faithful for every case the old hook actually hit.

Notes

  • useMultiPhaseKycFlow + useSumsubKycFlow kept — the WebSDK lifecycle plumbing. Only the state-machine hooks went.
  • Interim hand-written types in src/types/capabilities.ts will be replaced by pnpm gen:api once Feat/request link preview #897's regenerated openapi.json is consumed.
  • Browser-rendering e2e (Playwright/chrome-devtools): blocked tonight on the local Chrome extension not being connected; HTTP-level e2e covers the contract.

🤖 drafted + reviewed + verified overnight. The agent's status-rewrite PRs (857612261 PART A, c7fabc594 PART B) were reviewed; one real regression (resume regionIntent regression) caught + fixed in #897 (74a01a16).

Hugo0 added 2 commits May 27, 2026 23:55
The pool-tier Manteca rail fixture modeled status:'requires-info', but the BE
resolver emits status:'enabled' (the rail IS usable for pay via the corporate
pool) with operations refining deposit/withdraw to requires-info. Conflating
pool-tier with requires-info status would have taught the consumer migration a
false model — and made hasEnabledRail('manteca') wrongly false for a pay-capable
rail. Fixture now mirrors BE exactly; hasEnabledRail('manteca')=true, canDo
pay=true / withdraw=false. 33 tests green.
@vercel

vercel Bot commented May 27, 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 May 28, 2026 12:15pm

Request Review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Migrates KYC gating to a capability model: adds capability types, useCapabilities (with polling), pure gate derivation, region and rejection utilities, identity hook, API/type updates, and rewires UI/pages/tests to use capability rails and gate helpers.

Changes

Capabilities-based verification and gating

Layer / File(s) Summary
Core capability types, hook, gate, and polling
src/types/capabilities.ts, src/hooks/useCapabilities.ts, src/utils/capability-gate.ts, src/utils/provider-rejection.utils.ts, src/utils/regions.utils.ts, src/types/api.generated.ts, src/hooks/query/user.ts
Adds capability and identity types, implements useCapabilities with polling and lookup helpers, pure gate derivation (deriveGate), provider-rejection and region utilities, and API/type updates to surface capabilities/identityVerification.
UI/pages/hooks rewires and modal updates
src/hooks/useIdentityVerification.ts, src/hooks/useTosGuard.ts, src/components/**, src/app/**, src/features/**, src/components/Kyc/**, src/hooks/**/tests, src/components/Home/WelcomeUnlockModal
Replaces legacy KYC hooks/imports with useCapabilities/useIdentityVerification and capability-gate helpers across pages and components; updates KYC modal variants, copy, and flows; updates tests to mock/use the new capability fixtures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • jjramirezn

@coderabbitai coderabbitai Bot added the enhancement New feature or request label May 27, 2026
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5875.09 → 5797.66 (-77.43)
Findings: -41 net (+301 new, -342 resolved)

🆕 New findings (301)

  • critical complexity — src/app/(mobile-ui)/qr-pay/page.tsx — CC 280, MI 53.27, SLOC 918
  • critical complexity — src/components/Claim/Link/Initial.view.tsx — CC 210, MI 51.21, SLOC 680
  • critical complexity — src/utils/general.utils.ts — CC 200, MI 57.46, SLOC 764
  • critical complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 146, MI 52.21, SLOC 511
  • critical complexity — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 114, MI 57.37, SLOC 342
  • critical complexity — src/components/Claim/Claim.tsx — CC 100, MI 53.99, SLOC 387
  • critical complexity — src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 94, MI 48.31, SLOC 388
  • critical complexity — src/components/Home/HomeHistory.tsx — CC 93, MI 58.45, SLOC 296
  • critical complexity — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 90, MI 52.44, SLOC 309
  • critical complexity — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 85, MI 57.56, SLOC 302
  • critical method-complexity — src/app/(mobile-ui)/qr-pay/page.tsx:81 — QRPayPage CC 70 SLOC 318
  • critical complexity — src/hooks/useMultiPhaseKycFlow.ts — CC 66, MI 58.03, SLOC 315
  • critical complexity — src/components/Claim/Link/SendLinkActionList.tsx — CC 61, MI 57.9, SLOC 173
  • critical complexity — src/components/Profile/views/UnlockedRegions.view.tsx — CC 61, MI 63.09, SLOC 164
  • critical complexity — src/components/Request/direct-request/views/Initial.direct.request.view.tsx — CC 57, MI 55.57, SLOC 167
  • critical complexity — src/components/Kyc/SumsubKycWrapper.tsx — CC 55, MI 60.66, SLOC 268
  • critical complexity — src/components/Common/CountryList.tsx — CC 53, MI 61.69, SLOC 121
  • critical complexity — src/hooks/useHomeCarouselCTAs.tsx — CC 53, MI 59.52, SLOC 235
  • critical complexity — src/context/PeanutDebug.tsx — CC 52, MI 49.37, SLOC 342
  • critical complexity — src/hooks/useCapabilities.ts — CC 51, MI 65.52, SLOC 206

…and 281 more.

✅ Resolved (342)

  • src/app/(mobile-ui)/qr-pay/page.tsx — CC 264, MI 52.95, SLOC 861
  • src/components/Claim/Link/Initial.view.tsx — CC 209, MI 50.94, SLOC 677
  • src/utils/general.utils.ts — CC 200, MI 57.44, SLOC 765
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 145, MI 51.88, SLOC 509
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 109, MI 56.8, SLOC 323
  • src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 94, MI 47.91, SLOC 376
  • src/components/Home/HomeHistory.tsx — CC 94, MI 58.66, SLOC 298
  • src/components/Claim/Claim.tsx — CC 93, MI 52.79, SLOC 366
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 88, MI 51.97, SLOC 304
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 83, MI 57.4, SLOC 294
  • src/app/(mobile-ui)/qr-pay/page.tsx:81 — QRPayPage CC 70 SLOC 307
  • src/hooks/useMultiPhaseKycFlow.ts — CC 65, MI 58.28, SLOC 287
  • src/components/Claim/Link/SendLinkActionList.tsx — CC 61, MI 57.95, SLOC 172
  • src/components/Request/direct-request/views/Initial.direct.request.view.tsx — CC 57, MI 55.61, SLOC 168
  • src/components/Kyc/SumsubKycWrapper.tsx — CC 55, MI 60.67, SLOC 268
  • src/context/PeanutDebug.tsx — CC 52, MI 49.3, SLOC 344
  • src/hooks/useHomeCarouselCTAs.tsx — CC 51, MI 58.83, SLOC 227
  • src/app/(mobile-ui)/history/page.tsx — CC 50, MI 57.98, SLOC 199
  • src/components/Profile/views/RegionsVerification.view.tsx — CC 50, MI 61.4, SLOC 132
  • src/app/(mobile-ui)/qr-pay/page.tsx — 83 commits, +895/-1038 lines since 6 months ago

…and 322 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/IdentityVerification/UnlockRegionModal.tsx 0.0 8.3 +8.3
src/components/Profile/views/UnlockedRegions.view.tsx 0.0 7.5 +7.5
src/hooks/useCapabilities.ts 0.0 7.3 +7.3
src/components/Home/WelcomeUnlockModal/index.tsx 0.0 6.2 +6.2
src/utils/regions.utils.ts 0.0 6.0 +6.0
src/utils/capability-gate.ts 0.0 5.9 +5.9
src/hooks/useTosGuard.ts 0.0 5.0 +5.0
src/hooks/useIdentityVerification.ts 0.0 4.7 +4.7
src/utils/provider-rejection.utils.ts 0.0 3.6 +3.6
src/types/capabilities.ts 0.0 1.0 +1.0
src/components/Global/KycVerifiedOrReviewModal/index.tsx 3.7 4.6 +0.8
src/hooks/useCrispUserData.ts 6.4 7.1 +0.7
src/interfaces/interfaces.ts 3.6 4.1 +0.5
src/app/(mobile-ui)/withdraw/manteca/page.tsx 24.1 23.5 -0.6
src/components/Kyc/states/KycCompleted.tsx 7.2 6.6 -0.6
src/components/Kyc/states/KycFailed.tsx 6.9 6.2 -0.7
src/components/Claim/Link/MantecaFlowManager.tsx 11.0 10.2 -0.8
src/components/Kyc/KycStatusItem.tsx 9.1 8.1 -1.0
src/utils/kyc-grouping.utils.ts 5.7 4.3 -1.3
src/components/Kyc/KycStatusDrawer.tsx 11.6 10.2 -1.4

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1175 ran, 0 failed, 0 skipped, 19.6s

📊 Coverage (unit)

metric %
statements 48.5%
branches 29.6%
functions 32.0%
lines 48.4%
⏱ 10 slowest test cases
time test
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every stamp stays within canvas at any count
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit 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 too long for US account
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 valid ETH address
0.1s src/components/Request/__tests__/request-states.test.tsx › API failure shows error message and toast
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (invalid characters)
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@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/useCapabilities.ts`:
- Around line 177-187: The interval callback can cause overlapping fetchUser()
invocations and unhandled rejections; add an in-flight guard (e.g.,
isFetchingRef or local isFetching boolean) checked at the top of the setInterval
callback to skip starting a new fetch while one is running, and run fetchUser
inside an async wrapper with try/catch/finally to await the call, handle/log any
rejection, and clear the guard in finally; keep using the existing cancelled,
hasPendingRef, timer and POLL_INTERVAL_MS symbols and still clearInterval(timer)
when !hasPendingRef.current or cancelled is true.
🪄 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: 96363d0d-f5e0-4172-9501-cc0feb86a3cf

📥 Commits

Reviewing files that changed from the base of the PR and between ae2a52f and 750aaa5.

📒 Files selected for processing (4)
  • src/hooks/useCapabilities.test.ts
  • src/hooks/useCapabilities.ts
  • src/interfaces/interfaces.ts
  • src/types/capabilities.ts

Comment thread src/hooks/useCapabilities.ts

@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 138-140: The KYC gate was broadened incorrectly by setting
hasKycApproval = isKycApproved (which means any enabled rail); restore the
original Bridge/Manteca-only semantics by computing hasKycApproval from
provider-scoped flags (e.g., use the existing bridge- and manteca-specific
booleans such as isUserKycApproved, isUserMantecaKycApproved, or provider flags
like isBridgeKycApproved || isMantecaKycApproved) and replace the current
assignment in useHomeCarouselCTAs (where hasKycApproval is defined) so
QR/payment CTAs only consider Bridge and Manteca KYC, not Rain or other rails.
🪄 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: dea1d4db-4317-4474-92c3-a4f9b6024abe

📥 Commits

Reviewing files that changed from the base of the PR and between 750aaa5 and 8c98a3e.

📒 Files selected for processing (11)
  • src/app/(mobile-ui)/home/page.tsx
  • src/components/Claim/Link/Initial.view.tsx
  • src/components/Claim/Link/MantecaFlowManager.tsx
  • src/components/Claim/Link/SendLinkActionList.tsx
  • src/components/Global/KycVerifiedOrReviewModal/index.tsx
  • src/components/Home/ActivationCTAs.tsx
  • src/components/Home/KycCompletedModal/index.tsx
  • src/features/payments/flows/contribute-pot/components/RequestPotActionList.tsx
  • src/features/payments/shared/components/PaymentMethodActionList.tsx
  • src/hooks/useActivationStatus.ts
  • src/hooks/useHomeCarouselCTAs.tsx

Comment thread src/hooks/useHomeCarouselCTAs.tsx Outdated

@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: 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)/qr-pay/__tests__/qr-pay-states.test.tsx:
- Around line 436-440: In capabilitiesForGate, replace the two untyped mocks
rails: any[] and restrictions: any[] with concrete types (e.g., Rail[] and
Restriction[] or the exact types used by your codebase such as
GateRail/GateRestriction) and import those types at the top of the test file (or
declare small local interfaces matching the fixture shape), then update the mock
fixture objects returned by rails and restrictions to satisfy those types so the
file passes `@typescript-eslint/no-explicit-any`; target the capabilitiesForGate
function and the rails and restrictions variables when making the change.

In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Around line 152-197: The memo currently treats missing user as
"REQUIRES_IDENTITY_VERIFICATION" before the one-shot fetch completes; add a
small settled flag to keep the KYC gate in LOADING until that fetch finishes:
create a ref like userFetchSettledRef (initially false), set it to true in the
finally block of the one-shot call inside the useEffect that calls fetchUser
(i.e., where hasRequestedUserFetchRef is set and fetchUser is awaited), and then
update the useMemo (which computes kycGateState) to return QrKycState.LOADING
when !user && !userFetchSettledRef.current (in addition to the existing
isLoadingCapabilities check) so the verify modal is not shown while the initial
auth fetch is in-flight.

In `@src/components/Profile/index.tsx`:
- Around line 28-40: The Profile component is incorrectly aliasing
useCapabilities().isKycApproved to isUserSumsubKycApproved (rail-level approval)
which changes the UI's identity-verified semantics; instead read a dedicated
identity-verification capability (e.g. identityVerified or
isUserSumsubKycApproved if that exact capability exists) from useCapabilities()
and use that value for the verified badge and "Regions & Verification"
highlight; if the capability does not exist yet, restore the original
Sumsub-specific field name in the hook or add a new capability on the
capabilities provider (and update useCapabilities and the provider/data mapping)
so the profile consumes a true identity-verified signal rather than the generic
isKycApproved.

In `@src/components/Profile/views/ProfileEdit.view.tsx`:
- Around line 19-25: The change replaced the identity-check flag with a
rail-enabled flag causing name/surname to lock incorrectly; revert the check in
ProfileEdit.view.tsx to use the identity verification signal (restore use of
isUserKycApproved or the proper identity flag) instead of isKycApproved from
useCapabilities(), and update any other occurrences mentioned (lines ~126-142)
so the name/surname lock logic uses the dedicated identity-verification boolean
(isUserKycApproved or the original identity-specific selector) rather than the
rail-enabled isKycApproved.
🪄 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: 99ed6bc0-a0ca-4020-b69d-45827ecd6e90

📥 Commits

Reviewing files that changed from the base of the PR and between 8c98a3e and f4eb9f7.

📒 Files selected for processing (9)
  • src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/components/Profile/components/ProfileHeader.tsx
  • src/components/Profile/index.tsx
  • src/components/Profile/views/ProfileEdit.view.tsx
  • src/constants/kyc.consts.ts
  • src/features/limits/views/LimitsPageView.tsx
  • src/hooks/useQrKycGate.ts
  • src/types/capabilities.ts
✅ Files skipped from review due to trivial changes (1)
  • src/hooks/useQrKycGate.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/capabilities.ts

Comment thread src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx Outdated
Comment thread src/app/(mobile-ui)/qr-pay/page.tsx Outdated
Comment thread src/components/Profile/index.tsx Outdated
Comment thread src/components/Profile/views/ProfileEdit.view.tsx Outdated
…batch A+B)

Replaces useBridgeTransferReadiness in the three bank-flow consumers
(add-money/bank, withdraw/bank, claim BankFlowManager) with an inline
gate derived from useCapabilities() via a shared deriveBridgeGate util.

The gate keeps the identical BridgeGateAction shape + priority order
(blocked > tos > fixable > needs_kyc > needs_enrollment > ready), so the
downstream gate.type branching and InitiateKycModal wiring are unchanged.
Bridge state is now read provider-scoped off the capability rails +
nextActions instead of raw user.rails/kycVerifications — matching the old
hook's provider-level (not per-method) semantics, verified against all 12
useBridgeTransferReadiness.test.ts cases.

Sumsub identity has no rail in the capability model, so the old
isUserSumsubKycApproved precondition maps to isKycApproved (any enabled
rail) — the same faithful proxy used by the already-migrated qr-pay /
ActivationCTAs / MantecaFlowManager consumers.

useMultiPhaseKycFlow is left untouched in all three consumers (the
orchestrator gets its own dedicated pass). useBridgeTransferReadiness is
not deleted (compiler-gated final step). Test mocks now drive the gate
through useCapabilities fixtures + the real deriveBridgeGate.

@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/utils/bridge-gate.utils.ts`:
- Around line 67-116: deriveBridgeGate currently treats an empty/absent
capabilities array as a concrete gate (e.g. needs_kyc); change it to signal
"capabilities not loaded" instead: update the deriveBridgeGate signature to
accept rails?: RailCapability[] (or nullable) and add a new BridgeGateAction
union member like 'unknown_capabilities' (or similar), then at the top of
deriveBridgeGate return { type: 'unknown_capabilities' } when rails is
null/undefined (or when a caller-provided loaded flag indicates capabilities are
not present) so callers can defer gating until real capabilities arrive; ensure
references to deriveBridgeGate and BridgeGateAction are updated accordingly.
🪄 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: 4244324c-aa58-4cc3-a519-8cbbcff79fa0

📥 Commits

Reviewing files that changed from the base of the PR and between f4eb9f7 and 0d7ecf9.

📒 Files selected for processing (5)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/add-money/__tests__/add-money-states.test.tsx
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/components/Claim/Link/views/BankFlowManager.view.tsx
  • src/utils/bridge-gate.utils.ts

Comment thread src/utils/bridge-gate.utils.ts Outdated
…ity-driven

The backend capability resolver now owns every KYC eligibility decision the FE
used to derive from raw user.rails / kycVerifications / bridge* fields. Rip out
the hooks that duplicated that logic and point their consumers at the thin
capability utils (deriveBridgeGate, deriveProviderRejection, deriveRegionAccess)
over useCapabilities().

Deleted: useUnifiedKycStatus, useKycStatus, useQrKycGate, useProviderRejectionStatus,
useRailStatusTracking, useBridgeTransferReadiness, useIdentityVerification (+ their
tests). ~1470 LoC net removed. Kept useMultiPhaseKycFlow / useSumsubKycFlow
(SDK orchestration, not state derivation).

New src/utils/provider-rejection.utils.ts extracts the per-provider fixable/blocked
derivation shared by KycStatusDrawer/KycStatusItem.

Raw-field removal from IUserProfile is deliberately NOT in this commit: the KYC
status drawer still reads bridgeKyc{StartedAt,ApprovedAt,RejectedAt,RejectionReasonString}
and the kycVerifications count for DISPLAY, which the capability model does not yet
carry. Removing them needs a contract decision (degrade the status card vs extend
the contract) — flagged for review, not decided unilaterally.

tsc green; full Jest suite green (1119 pass; only the preexisting content.test.ts fails).

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/hooks/useDetermineBankRequestType.ts (1)

31-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent stale async responses from overwriting newer request state.

At Line 50, getUserById can resolve after a newer effect run (triggered by Line 73 dependencies), but the older run still writes state. This can set an outdated requestType/requesterDetails.

💡 Suggested fix
 useEffect(() => {
+    let isCurrent = true
     const determineBankRequestType = async () => {
         const payerKycApproved = isKycApproved

         if (payerKycApproved) {
-            setRequestType(BankRequestType.UserBankRequest)
+            if (isCurrent) setRequestType(BankRequestType.UserBankRequest)
             return
         }
@@
             try {
                 const requesterDetails = await getUserById(requesterUserId)
+                if (!isCurrent) return
                 const requesterKycApproved = isUserKycVerified(requesterDetails)

                 if (requesterKycApproved) {
                     setRequesterDetails(requesterDetails)
                     setRequestType(BankRequestType.GuestBankRequest)
@@
             } catch (error) {
+                if (!isCurrent) return
                 if (user?.user.userId) {
                     setRequestType(BankRequestType.PayerKycNeeded)
                 } else {
                     setRequestType(BankRequestType.GuestKycNeeded)
                 }
             }
         }

         determineBankRequestType()
+        return () => {
+            isCurrent = false
+        }
     }, [user, requesterUserId, setRequesterDetails, isKycApproved])
🤖 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/hooks/useDetermineBankRequestType.ts` around lines 31 - 73, The async
determineBankRequestType effect can be overwritten by stale getUserById
responses; fix it by adding a cancellation guard (e.g., a local "active" boolean
or a requestId ref) inside the useEffect so you only call setRequestType and
setRequesterDetails when the current run is still active; implement the guard
inside determineBankRequestType (check before each set* after awaiting
getUserById) and flip/cancel it in the effect cleanup so outdated promises
cannot write stale state.
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

145-162: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use fresh capability data for gate checks after fetchUser().

At Line 145, user data is refreshed, but Lines 149-162 still evaluate gate and isUserBridgeKycUnderReview from the pre-refresh render. This can incorrectly block or allow submission right after KYC state transitions.

🤖 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/components/AddWithdraw/AddWithdrawCountriesList.tsx` around lines 145 -
162, After awaiting fetchUser(), the code must re-evaluate the user's capability
state instead of using stale locals; update the gating checks to use the fresh
data returned by fetchUser() (or re-read the relevant store/selectors) before
inspecting gate and isUserBridgeKycUnderReview. Specifically, after await
fetchUser() obtain the latest gate and KYC-under-review flag (instead of the
pre-render variables), then run the existing logic that calls guardWithTos(),
setIsKycModalOpen(true), setShowKycStatusModal(true) and returns { error:
'gate_blocked', silent: true } as needed so those branches operate on current
state.
🧹 Nitpick comments (3)
src/components/Home/KycCompletedModal/index.tsx (2)

111-128: 💤 Low value

Potential unnecessary effect re-runs due to unstable mantecaRails reference.

railsForProvider('manteca') returns a new array on each render, causing this effect to re-run even when the underlying rails haven't changed. Consider memoizing the lookup or moving it inside the effect.

♻️ Option: move railsForProvider call inside effect
-    const mantecaRails = railsForProvider('manteca')
     useEffect(() => {
         if (!isMantecaApproved) return

+        const mantecaRails = railsForProvider('manteca')
         let approvedCountry: string | undefined | null
         mantecaRails.forEach((rail) => {
             if (rail.status === 'enabled' && isMantecaSupportedCountryCode(rail.country)) {
                 approvedCountry = rail.country
             }
         })

         if (approvedCountry) {
             const _approvedCountryData = countryData.find(
                 (c) => c.iso2?.toUpperCase() === approvedCountry?.toUpperCase()
             )
             setApprovedCountryData(_approvedCountryData || null)
         }
-    }, [isMantecaApproved, mantecaRails])
+    }, [isMantecaApproved, railsForProvider])
🤖 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/components/Home/KycCompletedModal/index.tsx` around lines 111 - 128, The
effect re-runs every render because mantecaRails is recreated by
railsForProvider('manteca'); fix by either memoizing that call or moving it
inside the useEffect so the dependency list is stable: call
railsForProvider('manteca') inside the useEffect (or memoize it with useMemo
keyed to the underlying provider/rails data) and then iterate to
setApprovedCountryData using isMantecaApproved, isMantecaSupportedCountryCode,
countryData and setApprovedCountryData so the effect only depends on stable
values (e.g., isMantecaApproved and the memoized rails).

160-170: 💤 Low value

Fragile string comparison for filtering manteca QR item.

The filter at line 165 compares item.title against a hardcoded string. Since title can be a ReactNode, this comparison will always be false for JSX titles, potentially leaving duplicates. The manteca QR item at line 66 is a string, so it works, but this is brittle.

♻️ Consider adding an identifier field
 type VerificationUnlockItem = {
     title: React.ReactNode | string
     type: 'bridge' | 'manteca'
+    id?: string
 }

 // In getVerificationUnlockItems, add id to the manteca QR item:
     {
         title: 'QR Payments in Brazil and Argentina',
         type: 'manteca',
+        id: 'manteca-qr',
     },

 // Then filter by id:
-    return !(item.type === 'manteca' && item.title === 'QR Payments in Brazil and Argentina')
+    return !(item.type === 'manteca' && item.id === 'manteca-qr')
🤖 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/components/Home/KycCompletedModal/index.tsx` around lines 160 - 170, The
current filter on items uses a fragile string comparison of item.title (which
can be a ReactNode) to exclude the manteca QR Payments entry; instead, add and
use a stable identifier (e.g., item.id === 'manteca-qr') on the manteca item and
change the filter to exclude item.id === 'manteca-qr' when kycApprovalType ===
'all'; if you cannot add an id right away, guard the comparison by checking
typeof item.title === 'string' before comparing the title string (i.e., check
item.type === 'manteca' && typeof item.title === 'string' && item.title === 'QR
Payments in Brazil and Argentina'), but prefer adding item.id and filtering by
item.id for robustness.
src/components/Common/CountryList.tsx (1)

25-35: ⚡ Quick win

Duplicate isBridgeSupportedCountry implementation.

This function is identical to the one exported from src/utils/regions.utils.ts (line 169). Consider importing it to avoid maintaining two copies.

♻️ Import from regions.utils.ts
-import {
-    type CountryData,
-    countryData,
-    ALL_COUNTRIES_ALPHA3_TO_ALPHA2,
-    BRIDGE_ALPHA3_TO_ALPHA2,
-    PREFERRED_COUNTRY_ISO2,
-} from '`@/components/AddMoney/consts`'
+import {
+    type CountryData,
+    countryData,
+    ALL_COUNTRIES_ALPHA3_TO_ALPHA2,
+    PREFERRED_COUNTRY_ISO2,
+} from '`@/components/AddMoney/consts`'
+import { isBridgeSupportedCountry } from '`@/utils/regions.utils`'
 // ... other imports

-// precompute bridge alpha2 values for O(1) lookup
-const BRIDGE_ALPHA2_SET = new Set(Object.values(BRIDGE_ALPHA3_TO_ALPHA2))
-
-// MIGRATION-REVIEW: `isBridgeSupportedCountry` was sourced from `useIdentityVerification`, but it
-// is a PURE static lookup over BRIDGE_ALPHA3_TO_ALPHA2 — it never read any KYC/Sumsub state (the
-// migration map's "selectedLevel" note for CountryList was stale; this file never used it).
-// Inlined verbatim so the file no longer depends on the legacy hook. Behavior is identical.
-const isBridgeSupportedCountry = (code: string): boolean => {
-    const upper = code.toUpperCase()
-    return upper === 'US' || upper === 'MX' || upper in BRIDGE_ALPHA3_TO_ALPHA2 || BRIDGE_ALPHA2_SET.has(upper)
-}
🤖 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/components/Common/CountryList.tsx` around lines 25 - 35, Remove the
duplicate isBridgeSupportedCountry implementation and replace it with an import
from the existing utility; specifically delete the local
isBridgeSupportedCountry and the local BRIDGE_ALPHA2_SET computed from
BRIDGE_ALPHA3_TO_ALPHA2, then import and use the exported
isBridgeSupportedCountry from regions.utils (the version at
src/utils/regions.utils.ts that relies on BRIDGE_ALPHA3_TO_ALPHA2) so the file
no longer maintains a second copy—ensure all references that previously called
the local isBridgeSupportedCountry still call the imported function.
🤖 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`:
- Line 7: Update the import in useMultiPhaseKycFlow.ts to avoid the restricted
barrel import: replace the current import of KycModalPhase and IUserProfile from
'`@/interfaces`' with a direct import from the module where they are declared (the
interfaces.ts file) so it imports KycModalPhase and IUserProfile from the
explicit 'src/interfaces/interfaces' module; locate the import statement at the
top of useMultiPhaseKycFlow.ts and change it to directly reference the
interfaces.ts module.

---

Outside diff comments:
In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Around line 145-162: After awaiting fetchUser(), the code must re-evaluate the
user's capability state instead of using stale locals; update the gating checks
to use the fresh data returned by fetchUser() (or re-read the relevant
store/selectors) before inspecting gate and isUserBridgeKycUnderReview.
Specifically, after await fetchUser() obtain the latest gate and
KYC-under-review flag (instead of the pre-render variables), then run the
existing logic that calls guardWithTos(), setIsKycModalOpen(true),
setShowKycStatusModal(true) and returns { error: 'gate_blocked', silent: true }
as needed so those branches operate on current state.

In `@src/hooks/useDetermineBankRequestType.ts`:
- Around line 31-73: The async determineBankRequestType effect can be
overwritten by stale getUserById responses; fix it by adding a cancellation
guard (e.g., a local "active" boolean or a requestId ref) inside the useEffect
so you only call setRequestType and setRequesterDetails when the current run is
still active; implement the guard inside determineBankRequestType (check before
each set* after awaiting getUserById) and flip/cancel it in the effect cleanup
so outdated promises cannot write stale state.

---

Nitpick comments:
In `@src/components/Common/CountryList.tsx`:
- Around line 25-35: Remove the duplicate isBridgeSupportedCountry
implementation and replace it with an import from the existing utility;
specifically delete the local isBridgeSupportedCountry and the local
BRIDGE_ALPHA2_SET computed from BRIDGE_ALPHA3_TO_ALPHA2, then import and use the
exported isBridgeSupportedCountry from regions.utils (the version at
src/utils/regions.utils.ts that relies on BRIDGE_ALPHA3_TO_ALPHA2) so the file
no longer maintains a second copy—ensure all references that previously called
the local isBridgeSupportedCountry still call the imported function.

In `@src/components/Home/KycCompletedModal/index.tsx`:
- Around line 111-128: The effect re-runs every render because mantecaRails is
recreated by railsForProvider('manteca'); fix by either memoizing that call or
moving it inside the useEffect so the dependency list is stable: call
railsForProvider('manteca') inside the useEffect (or memoize it with useMemo
keyed to the underlying provider/rails data) and then iterate to
setApprovedCountryData using isMantecaApproved, isMantecaSupportedCountryCode,
countryData and setApprovedCountryData so the effect only depends on stable
values (e.g., isMantecaApproved and the memoized rails).
- Around line 160-170: The current filter on items uses a fragile string
comparison of item.title (which can be a ReactNode) to exclude the manteca QR
Payments entry; instead, add and use a stable identifier (e.g., item.id ===
'manteca-qr') on the manteca item and change the filter to exclude item.id ===
'manteca-qr' when kycApprovalType === 'all'; if you cannot add an id right away,
guard the comparison by checking typeof item.title === 'string' before comparing
the title string (i.e., check item.type === 'manteca' && typeof item.title ===
'string' && item.title === 'QR Payments in Brazil and Argentina'), but prefer
adding item.id and filtering by item.id for robustness.
🪄 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: 67885d1e-70e3-4077-9344-c8c993b669a1

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7ecf9 and 6c8fab0.

📒 Files selected for processing (30)
  • src/app/(mobile-ui)/add-money/__tests__/add-money-states.test.tsx
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/components/AddMoney/components/MantecaAddMoney.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/Common/CountryList.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Home/KycCompletedModal/index.tsx
  • src/components/IdentityVerification/StartVerificationModal.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/states/KycProviderRejection.tsx
  • src/components/Profile/views/RegionsVerification.view.tsx
  • src/features/limits/views/LimitsPageView.tsx
  • src/hooks/__tests__/kyc-withdrawal-gate.test.tsx
  • src/hooks/__tests__/useBridgeTransferReadiness.test.ts
  • src/hooks/__tests__/useProviderRejectionStatus.test.ts
  • src/hooks/__tests__/useQrKycGate.test.ts
  • src/hooks/useBridgeTransferReadiness.ts
  • src/hooks/useDetermineBankClaimType.ts
  • src/hooks/useDetermineBankRequestType.ts
  • src/hooks/useIdentityVerification.tsx
  • src/hooks/useKycStatus.tsx
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/hooks/useProviderRejectionStatus.ts
  • src/hooks/useQrKycGate.ts
  • src/hooks/useRailStatusTracking.ts
  • src/hooks/useUnifiedKycStatus.ts
  • src/utils/provider-rejection.utils.ts
  • src/utils/regions.utils.ts
💤 Files with no reviewable changes (11)
  • src/hooks/tests/useQrKycGate.test.ts
  • src/hooks/tests/kyc-withdrawal-gate.test.tsx
  • src/hooks/useKycStatus.tsx
  • src/hooks/useRailStatusTracking.ts
  • src/hooks/useProviderRejectionStatus.ts
  • src/hooks/tests/useBridgeTransferReadiness.test.ts
  • src/hooks/useUnifiedKycStatus.ts
  • src/hooks/useBridgeTransferReadiness.ts
  • src/hooks/useIdentityVerification.tsx
  • src/hooks/tests/useProviderRejectionStatus.test.ts
  • src/hooks/useQrKycGate.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/IdentityVerification/StartVerificationModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(mobile-ui)/add-money/tests/add-money-states.test.tsx

Comment thread src/hooks/useMultiPhaseKycFlow.ts Outdated
Hugo0 added 4 commits May 28, 2026 02:38
Mirrors the BE provider-agnostic identity read-model (peanut-api-ts #897):
IdentityVerification type + IUserProfile.identityVerification (top-level sibling
of capabilities) + a thin useIdentityVerification() selector.

Foundation for rewriting the KYC status surfaces to read {identityVerification,
capabilities} only — provider-blind. Additive; nothing consumes it yet.
Rewrite the KYC status card + drawer + state cards to read the
provider-agnostic identityVerification read-model via useIdentityVerification().
The identity status (processing/verified/action_required/failed) is the user's
ONE document check — no provider names, no bridgeKyc*/kycVerifications/rails reads.

KycStatusItem is now prop-free for status (position/className only); the timeline
entry is a bare marker (buildKycHistoryEntry gates on status !== 'not_started').
State cards take provider-agnostic props sourced from identity fields
(submittedAt/reviewedAt/actionMessage/rejectLabels). The per-provider rejection
and bridge-additional-docs branches are gone — those are capability-driven and
shown where the user uses the rail, so KycProviderRejection/KycRequiresDocuments
(and the now-unused KycNotStarted) are deleted.

useMultiPhaseKycFlow + SumsubKycModals (the SDK launch plumbing) are kept.
…c gate)

Remove bridgeKycStatus/bridgeKycStartedAt/bridgeKycApprovedAt/bridgeKycRejectedAt/
bridgeKycRejectionReasonString/kycVerifications from IUserProfile.user — the
current user's own KYC is now provider-blind. tsc is the completeness proof.

Migrate each straggler by intent:
- Current-user GATING (Claim bank-step redirect, post-signup redirect) → reads
  useCapabilities().isKycApproved.
- COUNTERPARTY reads (getUserById = the link sender / request requester) keep
  working via a new CounterpartyUser type — a kept concern parallel to
  Contact.bridgeKycStatus. The two bank-flow contexts + BankFlowManager's guest
  branch verify that OTHER user's KYC, not the current user's (logged-in
  readiness is already gated upstream by the capability deriveBridgeGate).

Contact.bridgeKycStatus and the IUserRail/IUserKycVerification type defs are kept.

DATA GAP (flagged, not fabricated): useCrispUserData's Manteca providerUserId
came from the removed raw kycVerifications. Neither read-model carries provider
account ids; it was an internal support-dashboard link only (not user-facing, not
a gate), so it degrades to undefined pending a backend-exposed provider id.
…idgeKycStatus)

Final piece of the provider-blind FE: the counterparty (link sender, request
requester) no longer carries raw provider KYC on the FE. CounterpartyUser is
now User + a single BE-computed boolean — `canReceiveBankOfframp` — which is
exactly the gate the guest claim-to-bank flow needs (off-ramping THROUGH the
sender's Bridge customer requires an enabled Bridge bank rail).

- CounterpartyUser: drop bridgeKycStatus/kycVerifications, add canReceiveBankOfframp.
- BankFlowManager guest branch + useDetermineBank{Claim,Request}Type: read the
  capability boolean from getUserById instead of `bridgeKycStatus === 'approved'`.
- ClaimBankFlowContext: drop the orphaned senderKycStatus context state (no
  external consumers; carried the now-unused BridgeKycStatus).

The wider "is verified" badge (kyc.consts.isUserKycVerified, used by send/request/
contacts/public-profile) still reads raw fields across other counterparty
endpoints (Contact, ApiUser); refining those is a tracked follow-up.

tsc green; FE suite 1157 pass (only preexisting content.test.ts fails).
Hugo0 added 3 commits May 28, 2026 06:11
…te gate

Six fixes flagged by CodeRabbit on the rip-out PR.

- **Profile + ProfileEdit** read `useIdentityVerification().isVerified`
  for the verified badge + name-field lock instead of
  `useCapabilities().isKycApproved`. The latter means "any enabled rail"
  (including Rain), which would falsely flip the identity-verified UI for a
  card-only user.
- **useHomeCarouselCTAs** scopes the QR/payment KYC gate to Bridge or
  Manteca's pay op, restoring the pre-rip-out semantics (Rain alone must
  NOT mark a user as QR-ready).
- **deriveBridgeGate** gains an explicit `loading` state + `isLoading`
  param. The three add-money / withdraw / claim-to-bank callers thread
  `isLoadingCapabilities` through + short-circuit when the gate returns
  `loading` so they don't flash a needs_kyc modal on top of an approved
  user whose data simply hasn't arrived.
- **qr-pay** holds the gate in LOADING until either the user is hydrated
  OR the fallback one-shot fetch settles, fixing a brief flash of the
  verify modal on cold loads.
- **useCapabilities** poll: adds an in-flight ref + `.catch().finally()`
  on `fetchUser()` so ticks can't stack when a fetch takes longer than the
  4s interval, and rejections don't surface as unhandled promise rejections.
  New test verifies the lock blocks an overlapping tick.
…rrel

`@/interfaces` is a barrel re-export — the build-perf rule (CLAUDE.md) bans
barrel imports because they force the bundler to load every re-export.
ESLint flagged this on the rip-out PR via CodeRabbit.
CodeRabbit flagged `any[]` for the rails and restrictions fixture arrays.
Mirroring the full RailCapability/CapabilityRestriction types here would
force every fixture to supply method/country/currency + a non-nullable
reason message that the page never reads. Defined local TestRail /
TestRestriction subsets covering only what the consumer touches.
Hugo0 added 2 commits May 28, 2026 11:43
…fier

Holistic rip-out of provider-name leakage in FE consumers. Replaces the
historical Bridge/Manteca string-literal queries (`hasEnabledRail('bridge')`,
`railsForProvider('bridge')`, `deriveBridgeGate`) with ONE primitive:

    useCapabilities().gateFor(op, scope?) → GateState

where scope composes by channel ('bank' | 'card' | 'qr-only'), country,
method, or railId. Every UI site that used to ask "is the user verified
on Bridge?" now asks "can the user deposit via bank-channel here?" — same
answer, but provider-blind by construction and Manteca's PIX_BR bank rail
naturally included (closing a latent bug in the old Bridge-only gate).

New primitives in src/utils/:
- rail-channel.ts   single source of truth for method → channel mapping
                    (BANK_METHODS, QR_ONLY_METHODS, CARD_METHODS). Single
                    place to add a new method.
- capability-gate.ts  pure deriveGate(state, op, scope) + GateState union
                    (ready / pending / accept-tos / fixable-rejection /
                    blocked-rejection / needs-identity / needs-enrollment
                    / loading). Hook seam: useCapabilities().gateFor().

useCapabilities exposes:
- gateFor(op, scope?)              the new primitive
- bankRails(opts?)                 provider-blind bank summary
- channelOf(rail)                  classifier delegate
- enabledCountriesFor(op?)         for region-aware UI copy

CodeRabbit semantic fix folded in: the gate's "identity verified"
precondition now reads `identityVerification.status === 'verified'`
(Sumsub-cleared) instead of `isKycApproved` (any-rail-enabled, which
falsely flips for card-only / pool-tier users).

Migrated 18 leakage sites onto the new primitives:
- BankFlowManager, add-money/[country]/bank, withdraw/[country]/bank,
  AddWithdrawCountriesList → useGate('deposit'|'withdraw', {channel:'bank'})
- useHomeCarouselCTAs, ActivationCTAs, KycVerifiedOrReviewModal,
  KycCompletedModal, LimitsPageView, Claim/Initial.view,
  PaymentMethodActionList → bankRails() / canDo('pay') / channelOf()
- ProfileHeader → useIdentityVerification().isVerified (was reading
  rail-approval; fixed to read identity)

Deleted:
- src/utils/bridge-gate.utils.ts
- src/hooks/useBridgeTosGuard.ts → renamed src/hooks/useTosGuard.ts
  (provider-blind; reads gate.kind === 'accept-tos')
- src/hooks/useBridgeTosStatus.ts (folded into useTosGuard)

KEPT (legitimate provider names):
- services/{manteca,rain}.ts, utils/bridge.utils.ts (wire APIs)
- MantecaFlowManager, MantecaAddMoney, withdraw/manteca/page.tsx
  (Manteca's pool→full tier upgrade is provider-unique UX)
- BridgeTosStep.tsx (renders Bridge's ToS iframe — provider adapter)

Tests: 1160/1164 FE Jest green (4 pre-existing skipped). tsc clean.

Follow-up: BE schema — promoting the channel classifier to a `channel`
field on RailCapability would let us delete src/utils/rail-channel.ts
and rely on a single BE source of truth. Tracked separately; the FE
classifier is a strict superset, so the upgrade is non-breaking.
Users care about CAPABILITIES (unlock card, unlock bank deposits, unlock QR
payments), not about an abstract "verification" state. The only legitimate
"verified" surface is the profile cosmetic badge — proof that a counterparty's
name on the platform is real. Elsewhere we say "unlock", "ready", or specify
exactly what's gated.

Modal copy (titles + descriptions):
  KycCompletedModal:        "Verification completed!"        → "🎉 You're unlocked"
                            "You now have access to:"        → "You can now:"
  KycVerifiedOrReviewModal: "Your verification is under review" → "Almost there"
                            "(generic) under review …"       → "Your bank deposits will be ready shortly …"
                            "You're already verified"        → "You're all set"
                            "(generic) verified …"           → "Your account is ready to go."
  KycFailedModal:           "Verification failed"            → "We couldn't verify your ID"
                            "Verification unsuccessful"      → "Let's try that again"
                            "Retry verification"             → "Try again"
  KycProcessingModal:       "Verification in progress"       → "Setting up your account…"
  KycActionRequiredModal:   "Action needed"                  → "One more step"
                            "Re-submit verification"         → "Continue"
  KycVerificationInProgressModal:
                            "We're verifying your identity"  → "Almost there"
                            "Identity verified!" (TOS step)  → "One more step"
                            "All set!"                        → "You're unlocked"
  InitiateKycModal:         "Verify your identity" default   → "Unlock your account"
                            "Submit local ID"                → "Unlock {region}"
                            "Verification issue"             → "We couldn't unlock this"
                            "Start Verification"             → "Unlock now"
  StartVerificationModal:   "Verify now"                     → "Unlock now"
                            (region copy capability-shaped instead of verification-shaped)

CTA copy:
  ActivationCTAs `verify` step:   "Verify to get started"    → "Unlock payments"
                                  "Verify now"               → "Unlock now"
  Home carousel kyc-prompt:       "Verify your account to use …" → "Confirm your ID to pay with …"
  qr-pay/page.tsx require-verify: "Verify your identity to continue" → "Unlock QR payments"
                                  "Verify now"               → "Unlock now"
  qr-pay/page.tsx in-progress:    "Complete your verification" → "Almost there"
                                  "Continue verification"    → "Continue"
  GuestVerificationModal:         "This method requires verification" → "Unlock this payment method"
                                  "Start verification"       → "Get started"

Navigation:
  Profile menu item:        "Regions & Verification"         → "Unlocked regions"
  RegionsVerification view: page title "Regions & Verification" → "Unlocked regions"
                            empty state copy: capability-shaped

Exit dialogs (SumsubKycWrapper / IframeWrapper):
                            "Having trouble verifying?"      → "Need a hand?"
                            "Stop verification?"             → "Exit and lose progress?"
                            "Are you sure you want to exit?" → "Exit for now?"
                            "Stop verification"              → "Exit"
                            "Continue verifying"             → "Keep going"

Other:
  RejectLabelsList default: "complete your verification"     → "confirm your ID"
  FiatLimitsLockedCard:     "Unlock fiat limits"             → "Unlock fiat payments"
                            "Fiat limits locked"             → "Locked"
                            "Complete identity verification" → "Confirm your ID"
                            "Verify identity"                → "Unlock now"
  ProfileHeader badge:      now reads useIdentityVerification().isVerified (Sumsub),
                            not rail-approval — the one surface where "Verified" is
                            legitimate UX (proof of name on platform).

Tests: 1160/1164 FE Jest green (qr-pay-states copy updated to new strings).
tsc clean. No logic changes — modal/CTA wiring is unchanged.

Component file renames (KycCompletedModal → WelcomeUnlockModal etc.) are
intentionally deferred — the BE `showKycCompletedModal` field name would need
coordinated rename. Copy refactor stands alone.
Hugo0 added 2 commits May 28, 2026 12:18
Pairs with the BE commit that promotes channel categorization to a field on
RailCapability. The FE no longer enumerates which methods are bank/card/qr
on its own — `rail.channel` is the single source of truth, computed at
resolve time on the server.

  - delete src/utils/rail-channel.ts (BE owns this now)
  - capability-gate.ts reads `rail.channel === scope.channel` directly
  - useCapabilities.ts: bankRails() filters by channel; channelOf returns
    the BE-emitted value (and is now non-nullable — BE throws on unknown
    methods so undefined never travels here)
  - types: RailCapability gains `channel: RailChannel` (strict superset of
    the FE-side type that was here before)
  - useCapabilities.test.ts fixtures get the new field

Tests: 1160/1164 FE Jest green (4 pre-existing skipped). tsc clean.
Filename + symbol renames to match the copy refactor:

  src/components/Home/KycCompletedModal/         → WelcomeUnlockModal/
  src/components/IdentityVerification/StartVerificationModal.tsx → UnlockRegionModal.tsx
  src/components/Profile/views/RegionsVerification.view.tsx       → UnlockedRegions.view.tsx

Component identifiers updated to match. Two import-path call sites
(home page lazy-load, identity-verification page) follow.

Kept verbatim (BE-controlled or contract-shaped):
  - user.user.showKycCompletedModal — BE field, renaming would be a
    coordinated BE+FE schema change for zero user-visible benefit
  - api.generated.ts — generated from BE OpenAPI
  - interfaces.ts IUserProfile.showKycCompletedModal — mirrors the BE field

`git mv` for history preservation; no logic changes. Tests: 1160/1164 green.
tsc clean.
@Hugo0

Hugo0 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

The Manteca pool→full upgrade modal in `MantecaAddMoney` + `withdraw/manteca/page` was gating on `isKycApproved` (any-rail-enabled, the same latent bug pattern CodeRabbit flagged on Profile/ProfileEdit). The pool tier is reached the moment Sumsub clears the human; rail-approval elsewhere is unrelated. Switched both call sites to `useIdentityVerification().isVerified` to read the identity signal directly.

Tests: 1160/1164 FE Jest green; tsc clean; prettier --check clean.
Switched the bank-rail filter in regions.utils.isVerifiedForCountry from
`rail.provider === 'manteca' / 'bridge'` to `rail.channel === 'bank'`,
matching the rest of the FE that now consumes the BE-emitted channel.
Same semantics — Manteca's PIX_BR / BANK_TRANSFER_AR were already
channel='bank' on the wire — just one less provider literal in the FE.

deriveRegionAccess() in the same file is intentionally left provider-aware:
the region→provider mapping (US/EU/MX→Bridge, AR/BR→Manteca) is a real
product fact that codifies which provider serves each region. Renaming it
to channel terms would obscure the business reality. Flagged as a possible
follow-up if the region→provider mapping ever generalises.

Tests: 1160/1164 FE Jest green; tsc clean; prettier --check clean.

@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: 7

♻️ Duplicate comments (1)
src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx (1)

14-14: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused CapabilityRestriction import.

ESLint flags this as unused. Only RailCapability is needed for the TestRail type derivation.

🧹 Proposed fix
-import type { RailCapability, CapabilityRestriction } from '`@/types/capabilities`'
+import type { RailCapability } from '`@/types/capabilities`'
🤖 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)/qr-pay/__tests__/qr-pay-states.test.tsx at line 14,
Remove the unused CapabilityRestriction import from the import statement so only
RailCapability is imported; update the import that currently reads "import type
{ RailCapability, CapabilityRestriction } from '`@/types/capabilities`'" to drop
CapabilityRestriction and confirm any type alias/derivation like TestRail
continues to reference only RailCapability.
🧹 Nitpick comments (6)
src/components/Claim/Claim.tsx (1)

426-431: 💤 Low value

Consider adding searchParams to the dependency array.

The effect reads stepFromURL from searchParams.get('step'), but the dependency array only includes isKycApproved. If searchParams changes (e.g., user navigates with different query params), the effect won't re-run. This may be intentional if you only want to trigger on initial KYC approval, but if the redirect should also fire when step=bank appears in the URL later, add searchParams to deps.

🤖 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/components/Claim/Claim.tsx` around lines 426 - 431, The effect uses
searchParams.get('step') but only lists isKycApproved in the dependency array,
so it won't re-run when query params change; update the useEffect dependency
array to include searchParams (or searchParams.get('step') value) so the effect
re-evaluates when the URL changes and still sets
setClaimBankFlowStep(ClaimBankFlowStep.BankCountryList) when isKycApproved and
stepFromURL === 'bank'; alternatively, if only the initial KYC approval should
trigger this, add a comment explaining the intentional omission to avoid lint
complaints.
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)

87-94: 💤 Low value

Static function in useEffect dependency array.

isBridgeSupportedCountry is a static import, not a reactive value. Including it in the dependency array is harmless but unnecessary — ESLint's exhaustive-deps rule may flag this in stricter configs.

🤖 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)/withdraw/[country]/bank/page.tsx around lines 87 - 94,
The useEffect currently lists the static import isBridgeSupportedCountry in its
dependency array; remove isBridgeSupportedCountry from the dependencies so the
effect depends only on reactive values (country and router). Update the
useEffect signature that references useEffect, getCountryFromPath,
isBridgeSupportedCountry and router.replace to: useEffect(() => { ... },
[country, router]) and keep the same runtime check using
isBridgeSupportedCountry inside the effect (no code movement), which satisfies
exhaustive-deps without including the static function.
src/components/Claim/__tests__/claim-states.test.tsx (1)

134-138: ⚡ Quick win

Align getUserById mock with the new counterparty contract.

This mock still returns only isVerified; adding canReceiveBankOfframp (and optionally bridgeCustomerId where relevant) will keep this suite aligned with the capability-based flow and reduce false positives.

Suggested mock update
 jest.mock('`@/app/actions/users`', () => ({
-    getUserById: jest.fn(() => Promise.resolve({ isVerified: false })),
+    getUserById: jest.fn(() =>
+        Promise.resolve({
+            isVerified: false,
+            canReceiveBankOfframp: false,
+        })
+    ),
 }))
🤖 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/components/Claim/__tests__/claim-states.test.tsx` around lines 134 - 138,
The getUserById mock in the test currently returns only isVerified; update the
mocked return object for getUserById (in
src/components/Claim/__tests__/claim-states.test.tsx) to include
canReceiveBankOfframp: false (and add bridgeCustomerId: null where relevant) so
the mock matches the new counterparty contract and prevents capability-based
flow mismatches in assertions; keep isVerified: false to preserve existing
behavior.
src/components/IdentityVerification/UnlockRegionModal.tsx (1)

49-55: 💤 Low value

Consider renaming the interface to match the component.

The interface is still named StartVerificationModalProps while the component was renamed to UnlockRegionModal. Consider renaming to UnlockRegionModalProps for consistency.

♻️ Suggested rename
-interface StartVerificationModalProps {
+interface UnlockRegionModalProps {
     visible: boolean
     onClose: () => void
     onStartVerification: () => void
     selectedRegion: Region | null
     isLoading?: boolean
 }

-const UnlockRegionModal = ({
+const UnlockRegionModal = ({
     visible,
     onClose,
     onStartVerification,
     selectedRegion,
     isLoading,
-}: StartVerificationModalProps) => {
+}: UnlockRegionModalProps) => {

Also applies to: 57-57

🤖 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/components/IdentityVerification/UnlockRegionModal.tsx` around lines 49 -
55, Rename the props interface StartVerificationModalProps to
UnlockRegionModalProps and update all references to it (including the
UnlockRegionModal component's props annotation and any exports/imports) so the
type name matches the component; ensure you update the interface declaration,
the component signature (UnlockRegionModal), and any other files importing
StartVerificationModalProps to use UnlockRegionModalProps instead.
src/features/limits/views/LimitsPageView.tsx (1)

44-44: ⚡ Quick win

Scope pending-state evaluation by region to avoid false pending locks.

Line 44 treats any pending bank rail as global pending, but Lines 183-184 only apply that state to Europe/North America. This can incorrectly show pending/disable those regions when the pending rail belongs elsewhere.

Also applies to: 183-184

🤖 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/features/limits/views/LimitsPageView.tsx` at line 44, The current global
const isBankRailPending = bankRails().some(...) treats any pending rail as
affecting all regions; change this to a region-scoped check (e.g., create a
helper function isBankRailPendingForRegion(region) or compute a per-region map)
that filters bankRails() by the rail.region (or equivalent region identifier)
before checking status === 'pending' || 'requires-info', and then use that
region-scoped helper instead of the global isBankRailPending in the region
enable/disable logic (the places referencing isBankRailPending around the
Europe/North America UI logic).
src/services/users.ts (1)

7-7: ⚡ Quick win

Use direct import instead of barrel import.

ESLint flags @/interfaces as a restricted barrel import. Import AccountType from the specific module to improve build performance.

♻️ Proposed fix
-import { AccountType } from '`@/interfaces`'
+import { AccountType } from '`@/interfaces/interfaces`'
🤖 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/services/users.ts` at line 7, Replace the barrel import in
src/services/users.ts by importing AccountType directly from the module that
actually declares it: instead of "import { AccountType } from '`@/interfaces`'",
change to a direct import from the specific interfaces file that defines
AccountType (the concrete module that exports AccountType) so ESLint's
restricted-barrel rule is satisfied and build performance improves.
🤖 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/actions/users.ts`:
- Line 3: The file users.ts currently imports the type via the barrel import
"import { type CounterpartyUser } from '`@/interfaces`'"; replace that barrel
import with a direct import from the concrete module that actually exports
CounterpartyUser (the interfaces module that defines CounterpartyUser) so you
only import the type directly rather than pulling in the entire barrel—update
the import statement in src/app/actions/users.ts to import CounterpartyUser from
the module that declares it.

In `@src/components/Claim/Link/views/BankFlowManager.view.tsx`:
- Around line 186-197: The code calls getUserById and immediately reads
guestSender.canReceiveBankOfframp which can mis-handle error payloads; update
the branch around getUserById (the guestSender variable and subsequent checks)
to first detect and surface any error payload from getUserById (e.g., check for
an error field or instanceof Error and throw/return that error) before accessing
guestSender.canReceiveBankOfframp, then only perform the capability check;
likewise ensure userForOfframp validation (bridgeCustomerId) happens after
verifying the resolved user object is not an error payload.

In `@src/components/Global/KycVerifiedOrReviewModal/index.tsx`:
- Around line 18-20: The current derived flags (isUnderReview and the verified
branch around lines 26-33) treat an empty or unresolved allBankRails array as
"verified"; change the logic to first guard on allBankRails having at least one
rail and all rails having resolved statuses before claiming verified or
under-review. Concretely, update isUnderReview and the
isVerified/verified-branch conditions to require allBankRails.length > 0 and
that no rail.status is null/undefined/unresolved; treat empty or any
unresolved-status rails as the unresolved/loading case so the modal does not
show success prematurely. Ensure you reference and change the variables
isUnderReview and the verified check (the block using allBankRails.some/ every)
accordingly.

In `@src/components/Home/WelcomeUnlockModal/index.tsx`:
- Around line 82-88: The impression flag hasTrackedShow (used in the useEffect
that captures ANALYTICS_EVENTS.MODAL_SHOWN via posthog.capture when isOpen) is
never reset so subsequent open/close cycles won't emit MODAL_SHOWN; update the
modal logic to reset hasTrackedShow.current to false when the modal closes
(e.g., add a cleanup/effect that watches isOpen and sets hasTrackedShow.current
= false when isOpen becomes false) so each reopen can re-emit the analytics
event.

In `@src/components/Profile/components/ProfileHeader.tsx`:
- Line 35: The self-verification check in ProfileHeader
(isAuthenticatedUserVerified) uses a case-sensitive comparison which can fail on
differently-cased usernames; update the logic to compare normalized strings
(e.g., use toLowerCase() or toLocaleLowerCase() on both
authenticatedUser?.user.username and username, optionally trim whitespace) while
preserving the existing selfIsIdentityVerified guard so the final expression
remains: selfIsIdentityVerified && normalized(authenticatedUser?.user.username)
=== normalized(username).

In `@src/context/ClaimBankFlowContext.tsx`:
- Line 6: The import of Account and CounterpartyUser currently uses the
restricted barrel '`@/interfaces`' and must be changed to a direct module import
to satisfy no-restricted-imports; update the import statement that brings in
Account and CounterpartyUser (in the ClaimBankFlowContext file) to import those
types from their concrete interfaces module (replace '`@/interfaces`' with the
specific module that exports Account and CounterpartyUser).

In `@src/context/RequestFulfillmentFlowContext.tsx`:
- Line 6: Replace the restricted barrel import "import { type CounterpartyUser }
from '`@/interfaces`'" in RequestFulfillmentFlowContext.tsx with a direct import
from the concrete module that actually exports CounterpartyUser (e.g., the file
where CounterpartyUser is defined), so update the import to reference that
specific module path and keep the named type import as CounterpartyUser to
satisfy the linter/no-restricted-imports rule and CI.

---

Duplicate comments:
In `@src/app/`(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx:
- Line 14: Remove the unused CapabilityRestriction import from the import
statement so only RailCapability is imported; update the import that currently
reads "import type { RailCapability, CapabilityRestriction } from
'`@/types/capabilities`'" to drop CapabilityRestriction and confirm any type
alias/derivation like TestRail continues to reference only RailCapability.

---

Nitpick comments:
In `@src/app/`(mobile-ui)/withdraw/[country]/bank/page.tsx:
- Around line 87-94: The useEffect currently lists the static import
isBridgeSupportedCountry in its dependency array; remove
isBridgeSupportedCountry from the dependencies so the effect depends only on
reactive values (country and router). Update the useEffect signature that
references useEffect, getCountryFromPath, isBridgeSupportedCountry and
router.replace to: useEffect(() => { ... }, [country, router]) and keep the same
runtime check using isBridgeSupportedCountry inside the effect (no code
movement), which satisfies exhaustive-deps without including the static
function.

In `@src/components/Claim/__tests__/claim-states.test.tsx`:
- Around line 134-138: The getUserById mock in the test currently returns only
isVerified; update the mocked return object for getUserById (in
src/components/Claim/__tests__/claim-states.test.tsx) to include
canReceiveBankOfframp: false (and add bridgeCustomerId: null where relevant) so
the mock matches the new counterparty contract and prevents capability-based
flow mismatches in assertions; keep isVerified: false to preserve existing
behavior.

In `@src/components/Claim/Claim.tsx`:
- Around line 426-431: The effect uses searchParams.get('step') but only lists
isKycApproved in the dependency array, so it won't re-run when query params
change; update the useEffect dependency array to include searchParams (or
searchParams.get('step') value) so the effect re-evaluates when the URL changes
and still sets setClaimBankFlowStep(ClaimBankFlowStep.BankCountryList) when
isKycApproved and stepFromURL === 'bank'; alternatively, if only the initial KYC
approval should trigger this, add a comment explaining the intentional omission
to avoid lint complaints.

In `@src/components/IdentityVerification/UnlockRegionModal.tsx`:
- Around line 49-55: Rename the props interface StartVerificationModalProps to
UnlockRegionModalProps and update all references to it (including the
UnlockRegionModal component's props annotation and any exports/imports) so the
type name matches the component; ensure you update the interface declaration,
the component signature (UnlockRegionModal), and any other files importing
StartVerificationModalProps to use UnlockRegionModalProps instead.

In `@src/features/limits/views/LimitsPageView.tsx`:
- Line 44: The current global const isBankRailPending = bankRails().some(...)
treats any pending rail as affecting all regions; change this to a region-scoped
check (e.g., create a helper function isBankRailPendingForRegion(region) or
compute a per-region map) that filters bankRails() by the rail.region (or
equivalent region identifier) before checking status === 'pending' ||
'requires-info', and then use that region-scoped helper instead of the global
isBankRailPending in the region enable/disable logic (the places referencing
isBankRailPending around the Europe/North America UI logic).

In `@src/services/users.ts`:
- Line 7: Replace the barrel import in src/services/users.ts by importing
AccountType directly from the module that actually declares it: instead of
"import { AccountType } from '`@/interfaces`'", change to a direct import from the
specific interfaces file that defines AccountType (the concrete module that
exports AccountType) so ESLint's restricted-barrel rule is satisfied and build
performance improves.
🪄 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: eb08e680-f76f-4c44-b41f-69baccc2c07f

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7ecf9 and 7cd7b04.

📒 Files selected for processing (92)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/add-money/__tests__/add-money-states.test.tsx
  • src/app/(mobile-ui)/history/page.tsx
  • src/app/(mobile-ui)/home/page.tsx
  • src/app/(mobile-ui)/profile/identity-verification/page.tsx
  • src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/actions/users.ts
  • src/components/AddMoney/components/MantecaAddMoney.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/Claim/Claim.tsx
  • src/components/Claim/Link/Initial.view.tsx
  • src/components/Claim/Link/views/BankFlowManager.view.tsx
  • src/components/Claim/__tests__/claim-states.test.tsx
  • src/components/Common/CountryList.tsx
  • src/components/Global/GuestVerificationModal/index.tsx
  • src/components/Global/IframeWrapper/index.tsx
  • src/components/Global/KycVerifiedOrReviewModal/index.tsx
  • src/components/Global/PostSignupActionManager/index.tsx
  • src/components/Home/ActivationCTAs.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Home/KycCompletedModal/index.tsx
  • src/components/Home/WelcomeUnlockModal/index.tsx
  • src/components/IdentityVerification/UnlockRegionModal.tsx
  • src/components/Kyc/InitiateKycModal.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/KycVerificationInProgressModal.tsx
  • src/components/Kyc/RejectLabelsList.tsx
  • src/components/Kyc/SumsubKycWrapper.tsx
  • src/components/Kyc/modals/KycActionRequiredModal.tsx
  • src/components/Kyc/modals/KycFailedModal.tsx
  • src/components/Kyc/modals/KycProcessingModal.tsx
  • src/components/Kyc/states/KycActionRequired.tsx
  • src/components/Kyc/states/KycCompleted.tsx
  • src/components/Kyc/states/KycFailed.tsx
  • src/components/Kyc/states/KycNotStarted.tsx
  • src/components/Kyc/states/KycProcessing.tsx
  • src/components/Kyc/states/KycProviderRejection.tsx
  • src/components/Kyc/states/KycRequiresDocuments.tsx
  • src/components/Kyc/states/__tests__/KycStates.test.tsx
  • src/components/Profile/components/ProfileHeader.tsx
  • src/components/Profile/components/PublicProfile.tsx
  • src/components/Profile/index.tsx
  • src/components/Profile/views/ProfileEdit.view.tsx
  • src/components/Profile/views/UnlockedRegions.view.tsx
  • src/components/Request/direct-request/views/Initial.direct.request.view.tsx
  • src/components/Send/views/Contacts.view.tsx
  • src/constants/kyc.consts.ts
  • src/constants/manteca.consts.ts
  • src/context/ClaimBankFlowContext.tsx
  • src/context/PeanutDebug.tsx
  • src/context/RequestFulfillmentFlowContext.tsx
  • src/features/limits/components/FiatLimitsLockedCard.tsx
  • src/features/limits/views/LimitsPageView.tsx
  • src/features/payments/shared/components/PaymentMethodActionList.tsx
  • src/hooks/__tests__/kyc-withdrawal-gate.test.tsx
  • src/hooks/__tests__/useBridgeTransferReadiness.test.ts
  • src/hooks/__tests__/useProviderRejectionStatus.test.ts
  • src/hooks/__tests__/useQrKycGate.test.ts
  • src/hooks/query/user.ts
  • src/hooks/useBridgeTosGuard.ts
  • src/hooks/useBridgeTosStatus.ts
  • src/hooks/useBridgeTransferReadiness.ts
  • src/hooks/useCapabilities.test.ts
  • src/hooks/useCapabilities.ts
  • src/hooks/useCrispUserData.ts
  • src/hooks/useDetermineBankClaimType.ts
  • src/hooks/useDetermineBankRequestType.ts
  • src/hooks/useHomeCarouselCTAs.tsx
  • src/hooks/useIdentityVerification.ts
  • src/hooks/useIdentityVerification.tsx
  • src/hooks/useKycStatus.tsx
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/hooks/useProviderRejectionStatus.ts
  • src/hooks/useQrKycGate.ts
  • src/hooks/useRailStatusTracking.ts
  • src/hooks/useTosGuard.ts
  • src/hooks/useUnifiedKycStatus.ts
  • src/interfaces/interfaces.ts
  • src/services/users.ts
  • src/types/api.generated.ts
  • src/types/api.openapi.json
  • src/types/capabilities.ts
  • src/utils/capability-gate.ts
  • src/utils/general.utils.ts
  • src/utils/kyc-grouping.utils.ts
  • src/utils/manteca-restriction.utils.ts
  • src/utils/provider-rejection.utils.ts
  • src/utils/regions.utils.ts
💤 Files with no reviewable changes (20)
  • src/hooks/useBridgeTosGuard.ts
  • src/components/Kyc/states/KycNotStarted.tsx
  • src/hooks/tests/useQrKycGate.test.ts
  • src/components/Kyc/states/KycRequiresDocuments.tsx
  • src/hooks/useBridgeTosStatus.ts
  • src/hooks/tests/useProviderRejectionStatus.test.ts
  • src/hooks/useKycStatus.tsx
  • src/hooks/tests/kyc-withdrawal-gate.test.tsx
  • src/hooks/tests/useBridgeTransferReadiness.test.ts
  • src/utils/manteca-restriction.utils.ts
  • src/hooks/useBridgeTransferReadiness.ts
  • src/hooks/useUnifiedKycStatus.ts
  • src/constants/manteca.consts.ts
  • src/hooks/useProviderRejectionStatus.ts
  • src/hooks/useIdentityVerification.tsx
  • src/hooks/useQrKycGate.ts
  • src/hooks/useRailStatusTracking.ts
  • src/components/Home/KycCompletedModal/index.tsx
  • src/components/Kyc/states/KycProviderRejection.tsx
  • src/constants/kyc.consts.ts
✅ Files skipped from review due to trivial changes (8)
  • src/app/(mobile-ui)/profile/identity-verification/page.tsx
  • src/components/Kyc/RejectLabelsList.tsx
  • src/components/Kyc/modals/KycProcessingModal.tsx
  • src/components/Kyc/SumsubKycWrapper.tsx
  • src/components/Kyc/KycVerificationInProgressModal.tsx
  • src/components/Kyc/modals/KycFailedModal.tsx
  • src/components/Global/IframeWrapper/index.tsx
  • src/features/limits/components/FiatLimitsLockedCard.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/components/Home/ActivationCTAs.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/tests/add-money-states.test.tsx
  • src/utils/provider-rejection.utils.ts
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/hooks/useCapabilities.test.ts
  • src/hooks/useCapabilities.ts
  • src/hooks/useHomeCarouselCTAs.tsx
  • src/components/AddMoney/components/MantecaAddMoney.tsx
  • src/components/Common/CountryList.tsx
  • src/utils/regions.utils.ts

Comment thread src/app/actions/users.ts
import { type ApiUser } from '@/services/users'
import { type AddBankAccountPayload, BridgeEndorsementType, type InitiateKycResponse } from './types/users.types'
import { type User } from '@/interfaces'
import { type CounterpartyUser } from '@/interfaces'

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where CounterpartyUser is defined
rg -n "export.*CounterpartyUser" src/interfaces

Repository: peanutprotocol/peanut-ui

Length of output: 141


Avoid barrel import for CounterpartyUser.

CounterpartyUser is exported from src/interfaces/interfaces.ts (line 227); update src/app/actions/users.ts to import it from that concrete module instead of the @/interfaces barrel (to satisfy no-restricted-imports and avoid pulling all re-exports).

🧰 Tools
🪛 ESLint

[error] 3-3: '@/interfaces' import is restricted from being used. Import from a specific file instead of the '@/interfaces' barrel — barrels force the bundler to load every re-export and hurt build perf. See CLAUDE.md.

(no-restricted-imports)

🤖 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/actions/users.ts` at line 3, The file users.ts currently imports the
type via the barrel import "import { type CounterpartyUser } from
'`@/interfaces`'"; replace that barrel import with a direct import from the
concrete module that actually exports CounterpartyUser (the interfaces module
that defines CounterpartyUser) so you only import the type directly rather than
pulling in the entire barrel—update the import statement in
src/app/actions/users.ts to import CounterpartyUser from the module that
declares it.

Comment on lines +186 to 197
const guestSender = isGuestFlow
? await getUserById(claimLinkData.sender?.userId ?? claimLinkData.senderAddress)
: user?.user

// handle error if user for offramp is not found
if (!userForOfframp || ('error' in userForOfframp && userForOfframp.error)) {
throw new Error(
(userForOfframp && typeof userForOfframp.error === 'string' && userForOfframp.error) ||
'Failed to get user info'
)
: null
if (isGuestFlow) {
if (!guestSender) throw new Error('Failed to get user info')
if (!guestSender.canReceiveBankOfframp) throw new Error('Sender cannot receive a bank off-ramp')
}

// handle error if user is not KYC approved
if (userForOfframp.bridgeKycStatus !== 'approved') throw new Error('User not KYC approved')
if (!userForOfframp?.bridgeCustomerId) throw new Error('User bridge customer ID not found')
const userForOfframp = isGuestFlow ? guestSender : user?.user
if (!userForOfframp) throw new Error('Failed to get user info')
if (!userForOfframp.bridgeCustomerId) throw new Error('User bridge customer ID not found')

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle getUserById error payload before capability checks.

This branch reads guestSender.canReceiveBankOfframp before validating whether guestSender is an error response, which can surface the wrong error and block valid retries on transient backend failures.

Suggested fix
-            const guestSender = isGuestFlow
-                ? await getUserById(claimLinkData.sender?.userId ?? claimLinkData.senderAddress)
-                : null
+            const guestSender = isGuestFlow
+                ? await getUserById(claimLinkData.sender?.userId ?? claimLinkData.senderAddress)
+                : null
             if (isGuestFlow) {
-                if (!guestSender) throw new Error('Failed to get user info')
-                if (!guestSender.canReceiveBankOfframp) throw new Error('Sender cannot receive a bank off-ramp')
+                if (!guestSender || ('error' in guestSender && guestSender.error)) {
+                    throw new Error(
+                        (guestSender && typeof guestSender.error === 'string' && guestSender.error) ||
+                            'Failed to get user info'
+                    )
+                }
+                if (!guestSender.canReceiveBankOfframp) {
+                    throw new Error('Sender cannot receive a bank off-ramp')
+                }
             }
🤖 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/components/Claim/Link/views/BankFlowManager.view.tsx` around lines 186 -
197, The code calls getUserById and immediately reads
guestSender.canReceiveBankOfframp which can mis-handle error payloads; update
the branch around getUserById (the guestSender variable and subsequent checks)
to first detect and surface any error payload from getUserById (e.g., check for
an error field or instanceof Error and throw/return that error) before accessing
guestSender.canReceiveBankOfframp, then only perform the capability check;
likewise ensure userForOfframp validation (bridgeCustomerId) happens after
verifying the resolved user object is not an error payload.

Comment on lines +18 to +20
const isUnderReview =
allBankRails.some((rail) => rail.status === 'pending') &&
!allBankRails.some((rail) => rail.status === 'enabled')

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid “all set” state when rails are unresolved/empty.

With an empty rail list, this falls into the verified branch immediately. Consider guarding empty/unresolved rails so the modal doesn’t claim success before capability state is known.

Also applies to: 26-33

🤖 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/components/Global/KycVerifiedOrReviewModal/index.tsx` around lines 18 -
20, The current derived flags (isUnderReview and the verified branch around
lines 26-33) treat an empty or unresolved allBankRails array as "verified";
change the logic to first guard on allBankRails having at least one rail and all
rails having resolved statuses before claiming verified or under-review.
Concretely, update isUnderReview and the isVerified/verified-branch conditions
to require allBankRails.length > 0 and that no rail.status is
null/undefined/unresolved; treat empty or any unresolved-status rails as the
unresolved/loading case so the modal does not show success prematurely. Ensure
you reference and change the variables isUnderReview and the verified check (the
block using allBankRails.some/ every) accordingly.

Comment on lines +82 to +88
const hasTrackedShow = useRef(false)
useEffect(() => {
if (isOpen && !hasTrackedShow.current) {
hasTrackedShow.current = true
posthog.capture(ANALYTICS_EVENTS.MODAL_SHOWN, { modal_type: MODAL_TYPES.KYC_COMPLETED })
}
}, [isOpen])

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset modal impression tracker on close/reopen cycles.

MODAL_SHOWN is emitted only once per mount because hasTrackedShow.current is never reset. If this modal can open multiple times, impressions are undercounted.

Suggested fix
     const hasTrackedShow = useRef(false)
     useEffect(() => {
         if (isOpen && !hasTrackedShow.current) {
             hasTrackedShow.current = true
             posthog.capture(ANALYTICS_EVENTS.MODAL_SHOWN, { modal_type: MODAL_TYPES.KYC_COMPLETED })
+        } else if (!isOpen) {
+            hasTrackedShow.current = false
         }
     }, [isOpen])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasTrackedShow = useRef(false)
useEffect(() => {
if (isOpen && !hasTrackedShow.current) {
hasTrackedShow.current = true
posthog.capture(ANALYTICS_EVENTS.MODAL_SHOWN, { modal_type: MODAL_TYPES.KYC_COMPLETED })
}
}, [isOpen])
const hasTrackedShow = useRef(false)
useEffect(() => {
if (isOpen && !hasTrackedShow.current) {
hasTrackedShow.current = true
posthog.capture(ANALYTICS_EVENTS.MODAL_SHOWN, { modal_type: MODAL_TYPES.KYC_COMPLETED })
} else if (!isOpen) {
hasTrackedShow.current = false
}
}, [isOpen])
🤖 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/components/Home/WelcomeUnlockModal/index.tsx` around lines 82 - 88, The
impression flag hasTrackedShow (used in the useEffect that captures
ANALYTICS_EVENTS.MODAL_SHOWN via posthog.capture when isOpen) is never reset so
subsequent open/close cycles won't emit MODAL_SHOWN; update the modal logic to
reset hasTrackedShow.current to false when the modal closes (e.g., add a
cleanup/effect that watches isOpen and sets hasTrackedShow.current = false when
isOpen becomes false) so each reopen can re-emit the analytics event.

// (Sumsub-cleared), matching the counterparty badge logic (`isVerified` on
// /users/:userId). Rail-approval is unrelated.
const { isVerified: selfIsIdentityVerified } = useIdentityVerification()
const isAuthenticatedUserVerified = selfIsIdentityVerified && authenticatedUser?.user.username === username

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a case-insensitive username comparison for self verification gating.

Line 35 compares usernames case-sensitively, which can hide the self verified badge when casing differs.

🔧 Suggested fix
-    const isAuthenticatedUserVerified = selfIsIdentityVerified && authenticatedUser?.user.username === username
+    const isAuthenticatedUserVerified =
+        selfIsIdentityVerified &&
+        authenticatedUser?.user.username?.toLowerCase() === username.toLowerCase()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isAuthenticatedUserVerified = selfIsIdentityVerified && authenticatedUser?.user.username === username
const isAuthenticatedUserVerified =
selfIsIdentityVerified &&
authenticatedUser?.user.username?.toLowerCase() === username.toLowerCase()
🤖 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/components/Profile/components/ProfileHeader.tsx` at line 35, The
self-verification check in ProfileHeader (isAuthenticatedUserVerified) uses a
case-sensitive comparison which can fail on differently-cased usernames; update
the logic to compare normalized strings (e.g., use toLowerCase() or
toLocaleLowerCase() on both authenticatedUser?.user.username and username,
optionally trim whitespace) while preserving the existing selfIsIdentityVerified
guard so the final expression remains: selfIsIdentityVerified &&
normalized(authenticatedUser?.user.username) === normalized(username).

import { type CountryData } from '../components/AddMoney/consts'
import { type TCreateOfframpResponse } from '@/services/services.types'
import { type Account, type User } from '@/interfaces'
import { type Account, type CounterpartyUser } from '@/interfaces'

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a direct interfaces import to avoid lint/CI failure.

Line 6 imports from the restricted @/interfaces barrel (no-restricted-imports). Please import Account and CounterpartyUser from the concrete interfaces module.

Suggested change
-import { type Account, type CounterpartyUser } from '`@/interfaces`'
+import { type Account, type CounterpartyUser } from '`@/interfaces/interfaces`'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { type Account, type CounterpartyUser } from '@/interfaces'
import { type Account, type CounterpartyUser } from '`@/interfaces/interfaces`'
🧰 Tools
🪛 ESLint

[error] 6-6: '@/interfaces' import is restricted from being used. Import from a specific file instead of the '@/interfaces' barrel — barrels force the bundler to load every re-export and hurt build perf. See CLAUDE.md.

(no-restricted-imports)

🤖 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/context/ClaimBankFlowContext.tsx` at line 6, The import of Account and
CounterpartyUser currently uses the restricted barrel '`@/interfaces`' and must be
changed to a direct module import to satisfy no-restricted-imports; update the
import statement that brings in Account and CounterpartyUser (in the
ClaimBankFlowContext file) to import those types from their concrete interfaces
module (replace '`@/interfaces`' with the specific module that exports Account and
CounterpartyUser).

import { type CountryData } from '@/components/AddMoney/consts'
import { type IOnrampData } from './OnrampFlowContext'
import { type User } from '@/interfaces'
import { type CounterpartyUser } from '@/interfaces'

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace restricted barrel import with a concrete interfaces module import.

Line 6 uses @/interfaces, which violates the configured no-restricted-imports rule and can fail CI.

Suggested change
-import { type CounterpartyUser } from '`@/interfaces`'
+import { type CounterpartyUser } from '`@/interfaces/interfaces`'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { type CounterpartyUser } from '@/interfaces'
import { type CounterpartyUser } from '`@/interfaces/interfaces`'
🧰 Tools
🪛 ESLint

[error] 6-6: '@/interfaces' import is restricted from being used. Import from a specific file instead of the '@/interfaces' barrel — barrels force the bundler to load every re-export and hurt build perf. See CLAUDE.md.

(no-restricted-imports)

🤖 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/context/RequestFulfillmentFlowContext.tsx` at line 6, Replace the
restricted barrel import "import { type CounterpartyUser } from '`@/interfaces`'"
in RequestFulfillmentFlowContext.tsx with a direct import from the concrete
module that actually exports CounterpartyUser (e.g., the file where
CounterpartyUser is defined), so update the import to reference that specific
module path and keep the named type import as CounterpartyUser to satisfy the
linter/no-restricted-imports rule and CI.

Pairs with the BE commit that renames NextActionKind 'bridge-tos' →
'accept-tos'. FE consumer-side changes:

  - types/capabilities.ts: union member updated
  - utils/capability-gate.ts: deriveGate's TOS-detection predicate now
    matches the new kind; GateState's 'accept-tos' kind name unchanged
    (was already kebab-case after the prior provider-blind commit)
  - hooks/useTosGuard.ts: doc comment updated
  - hooks/useCapabilities.test.ts + add-money-states.test.tsx: fixture
    nextActions updated

Tests: 1160/1164 FE Jest green; tsc clean; prettier --check clean.

The contract is now fully provider-blind on the wire: zero "bridge" /
"manteca" / "rain" literals reach FE consumers from the BE — they exist
only on RailCapability.provider (an internal field FE consumers don't
filter by) and inside the wire-adapter layer.
@Hugo0

Hugo0 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Actionable comments posted: 0

The home-screen activation CTA's `verify` step was showing even while the
user's Sumsub applicant was in `processing` — implying they still needed
to act. They don't: they already submitted, the BE is reviewing. The
identity-verification page already surfaces an "Almost there" modal for
this state; the home CTA was redundant + misleading.

ActivationCTAs now reads useIdentityVerification.isProcessing and short-
circuits the step memo when verify+processing. action_required is NOT
suppressed (that genuinely needs the user back).

Tests: 1160/1164 FE Jest green; tsc clean; prettier --check clean.
Two fixes from the bg audit pass for the same bug class ("UI nudges user
to act on something they already acted on and are waiting for"):

  PostSignupActionManager  reads useIdentityVerification().isVerified
                           instead of isKycApproved. The post-signup
                           "continue your claim" modal previously fired
                           for any-rail-enabled users including card-only
                           Rain — who can't satisfy a bank-claim redirect
                           URL. Identity-verified is the right signal.

  useHomeCarouselCTAs      isBankFlowInFlight broadened to isInFlight
                           and reads rails.some(pending|requires-info)
                           across ALL channels. Was bank-only, missing
                           pool-tier Manteca + QR-only mid-flight users.

(Audit also flagged a deeper near-bug in Claim/Link/Initial.view.tsx
handleIbanRecipient routing — flow restructure too invasive for this
pass; tracked as a follow-up. The cosmetic PaymentMethodActionList
badge change is product-shaped, also deferred.)

Tests: 1160/1164 FE Jest green; tsc clean; prettier --check clean.
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.

1 participant