feat(kyc): dumb FE — useCapabilities + consumer migration#2122
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughMigrates 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. ChangesCapabilities-based verification and gating
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
|
Code-analysis diffPainscore total: 5875.09 → 5797.66 (-77.43) 🆕 New findings (301)
…and 281 more. ✅ Resolved (342)
…and 322 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Actionable comments posted: 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
📒 Files selected for processing (4)
src/hooks/useCapabilities.test.tssrc/hooks/useCapabilities.tssrc/interfaces/interfaces.tssrc/types/capabilities.ts
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
src/app/(mobile-ui)/home/page.tsxsrc/components/Claim/Link/Initial.view.tsxsrc/components/Claim/Link/MantecaFlowManager.tsxsrc/components/Claim/Link/SendLinkActionList.tsxsrc/components/Global/KycVerifiedOrReviewModal/index.tsxsrc/components/Home/ActivationCTAs.tsxsrc/components/Home/KycCompletedModal/index.tsxsrc/features/payments/flows/contribute-pot/components/RequestPotActionList.tsxsrc/features/payments/shared/components/PaymentMethodActionList.tsxsrc/hooks/useActivationStatus.tssrc/hooks/useHomeCarouselCTAs.tsx
…es (batch D + E-trivial)
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(mobile-ui)/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
📒 Files selected for processing (9)
src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsxsrc/app/(mobile-ui)/qr-pay/page.tsxsrc/components/Profile/components/ProfileHeader.tsxsrc/components/Profile/index.tsxsrc/components/Profile/views/ProfileEdit.view.tsxsrc/constants/kyc.consts.tssrc/features/limits/views/LimitsPageView.tsxsrc/hooks/useQrKycGate.tssrc/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
…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.
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/add-money/__tests__/add-money-states.test.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/components/Claim/Link/views/BankFlowManager.view.tsxsrc/utils/bridge-gate.utils.ts
…es (keep WebSDK + API)
…f/country-list) to useCapabilities
…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).
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/hooks/useDetermineBankRequestType.ts (1)
31-73:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPrevent stale async responses from overwriting newer request state.
At Line 50,
getUserByIdcan resolve after a newer effect run (triggered by Line 73 dependencies), but the older run still writes state. This can set an outdatedrequestType/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 winUse fresh capability data for gate checks after
fetchUser().At Line 145, user data is refreshed, but Lines 149-162 still evaluate
gateandisUserBridgeKycUnderReviewfrom 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 valuePotential unnecessary effect re-runs due to unstable
mantecaRailsreference.
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 valueFragile string comparison for filtering manteca QR item.
The filter at line 165 compares
item.titleagainst a hardcoded string. Sincetitlecan be aReactNode, this comparison will always befalsefor 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 winDuplicate
isBridgeSupportedCountryimplementation.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
📒 Files selected for processing (30)
src/app/(mobile-ui)/add-money/__tests__/add-money-states.test.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/app/(mobile-ui)/withdraw/manteca/page.tsxsrc/components/AddMoney/components/MantecaAddMoney.tsxsrc/components/AddWithdraw/AddWithdrawCountriesList.tsxsrc/components/Common/CountryList.tsxsrc/components/Home/HomeHistory.tsxsrc/components/Home/KycCompletedModal/index.tsxsrc/components/IdentityVerification/StartVerificationModal.tsxsrc/components/Kyc/KycStatusDrawer.tsxsrc/components/Kyc/KycStatusItem.tsxsrc/components/Kyc/states/KycProviderRejection.tsxsrc/components/Profile/views/RegionsVerification.view.tsxsrc/features/limits/views/LimitsPageView.tsxsrc/hooks/__tests__/kyc-withdrawal-gate.test.tsxsrc/hooks/__tests__/useBridgeTransferReadiness.test.tssrc/hooks/__tests__/useProviderRejectionStatus.test.tssrc/hooks/__tests__/useQrKycGate.test.tssrc/hooks/useBridgeTransferReadiness.tssrc/hooks/useDetermineBankClaimType.tssrc/hooks/useDetermineBankRequestType.tssrc/hooks/useIdentityVerification.tsxsrc/hooks/useKycStatus.tsxsrc/hooks/useMultiPhaseKycFlow.tssrc/hooks/useProviderRejectionStatus.tssrc/hooks/useQrKycGate.tssrc/hooks/useRailStatusTracking.tssrc/hooks/useUnifiedKycStatus.tssrc/utils/provider-rejection.utils.tssrc/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
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).
…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.
…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.
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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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.
There was a problem hiding this comment.
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 winRemove unused
CapabilityRestrictionimport.ESLint flags this as unused. Only
RailCapabilityis needed for theTestRailtype 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 valueConsider adding
searchParamsto the dependency array.The effect reads
stepFromURLfromsearchParams.get('step'), but the dependency array only includesisKycApproved. IfsearchParamschanges (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 whenstep=bankappears in the URL later, addsearchParamsto 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 valueStatic function in useEffect dependency array.
isBridgeSupportedCountryis 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 winAlign
getUserByIdmock with the new counterparty contract.This mock still returns only
isVerified; addingcanReceiveBankOfframp(and optionallybridgeCustomerIdwhere 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 valueConsider renaming the interface to match the component.
The interface is still named
StartVerificationModalPropswhile the component was renamed toUnlockRegionModal. Consider renaming toUnlockRegionModalPropsfor 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 winScope 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 winUse direct import instead of barrel import.
ESLint flags
@/interfacesas a restricted barrel import. ImportAccountTypefrom 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
📒 Files selected for processing (92)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/add-money/__tests__/add-money-states.test.tsxsrc/app/(mobile-ui)/history/page.tsxsrc/app/(mobile-ui)/home/page.tsxsrc/app/(mobile-ui)/profile/identity-verification/page.tsxsrc/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsxsrc/app/(mobile-ui)/qr-pay/page.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/app/(mobile-ui)/withdraw/manteca/page.tsxsrc/app/actions/users.tssrc/components/AddMoney/components/MantecaAddMoney.tsxsrc/components/AddWithdraw/AddWithdrawCountriesList.tsxsrc/components/Claim/Claim.tsxsrc/components/Claim/Link/Initial.view.tsxsrc/components/Claim/Link/views/BankFlowManager.view.tsxsrc/components/Claim/__tests__/claim-states.test.tsxsrc/components/Common/CountryList.tsxsrc/components/Global/GuestVerificationModal/index.tsxsrc/components/Global/IframeWrapper/index.tsxsrc/components/Global/KycVerifiedOrReviewModal/index.tsxsrc/components/Global/PostSignupActionManager/index.tsxsrc/components/Home/ActivationCTAs.tsxsrc/components/Home/HomeHistory.tsxsrc/components/Home/KycCompletedModal/index.tsxsrc/components/Home/WelcomeUnlockModal/index.tsxsrc/components/IdentityVerification/UnlockRegionModal.tsxsrc/components/Kyc/InitiateKycModal.tsxsrc/components/Kyc/KycStatusDrawer.tsxsrc/components/Kyc/KycStatusItem.tsxsrc/components/Kyc/KycVerificationInProgressModal.tsxsrc/components/Kyc/RejectLabelsList.tsxsrc/components/Kyc/SumsubKycWrapper.tsxsrc/components/Kyc/modals/KycActionRequiredModal.tsxsrc/components/Kyc/modals/KycFailedModal.tsxsrc/components/Kyc/modals/KycProcessingModal.tsxsrc/components/Kyc/states/KycActionRequired.tsxsrc/components/Kyc/states/KycCompleted.tsxsrc/components/Kyc/states/KycFailed.tsxsrc/components/Kyc/states/KycNotStarted.tsxsrc/components/Kyc/states/KycProcessing.tsxsrc/components/Kyc/states/KycProviderRejection.tsxsrc/components/Kyc/states/KycRequiresDocuments.tsxsrc/components/Kyc/states/__tests__/KycStates.test.tsxsrc/components/Profile/components/ProfileHeader.tsxsrc/components/Profile/components/PublicProfile.tsxsrc/components/Profile/index.tsxsrc/components/Profile/views/ProfileEdit.view.tsxsrc/components/Profile/views/UnlockedRegions.view.tsxsrc/components/Request/direct-request/views/Initial.direct.request.view.tsxsrc/components/Send/views/Contacts.view.tsxsrc/constants/kyc.consts.tssrc/constants/manteca.consts.tssrc/context/ClaimBankFlowContext.tsxsrc/context/PeanutDebug.tsxsrc/context/RequestFulfillmentFlowContext.tsxsrc/features/limits/components/FiatLimitsLockedCard.tsxsrc/features/limits/views/LimitsPageView.tsxsrc/features/payments/shared/components/PaymentMethodActionList.tsxsrc/hooks/__tests__/kyc-withdrawal-gate.test.tsxsrc/hooks/__tests__/useBridgeTransferReadiness.test.tssrc/hooks/__tests__/useProviderRejectionStatus.test.tssrc/hooks/__tests__/useQrKycGate.test.tssrc/hooks/query/user.tssrc/hooks/useBridgeTosGuard.tssrc/hooks/useBridgeTosStatus.tssrc/hooks/useBridgeTransferReadiness.tssrc/hooks/useCapabilities.test.tssrc/hooks/useCapabilities.tssrc/hooks/useCrispUserData.tssrc/hooks/useDetermineBankClaimType.tssrc/hooks/useDetermineBankRequestType.tssrc/hooks/useHomeCarouselCTAs.tsxsrc/hooks/useIdentityVerification.tssrc/hooks/useIdentityVerification.tsxsrc/hooks/useKycStatus.tsxsrc/hooks/useMultiPhaseKycFlow.tssrc/hooks/useProviderRejectionStatus.tssrc/hooks/useQrKycGate.tssrc/hooks/useRailStatusTracking.tssrc/hooks/useTosGuard.tssrc/hooks/useUnifiedKycStatus.tssrc/interfaces/interfaces.tssrc/services/users.tssrc/types/api.generated.tssrc/types/api.openapi.jsonsrc/types/capabilities.tssrc/utils/capability-gate.tssrc/utils/general.utils.tssrc/utils/kyc-grouping.utils.tssrc/utils/manteca-restriction.utils.tssrc/utils/provider-rejection.utils.tssrc/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
| 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' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find where CounterpartyUser is defined
rg -n "export.*CounterpartyUser" src/interfacesRepository: 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.
| 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') | ||
|
|
There was a problem hiding this comment.
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.
| const isUnderReview = | ||
| allBankRails.some((rail) => rail.status === 'pending') && | ||
| !allBankRails.some((rail) => rail.status === 'enabled') |
There was a problem hiding this comment.
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.
| 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]) |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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' |
There was a problem hiding this comment.
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.
| 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' |
There was a problem hiding this comment.
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.
| 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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
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.
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:
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".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:
CounterpartyUser.canReceiveBankOfframp: boolean— fromgetUserById. Replaces the rawbridgeKycStatus !== '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 railpending.useIdentityVerification()— thin selector overuser.identityVerification, mirroring the BE projection.bridge-gate.utils.ts,provider-rejection.utils.ts,regions.utils.ts.identityVerification(provider-blind):KycStatusItem/KycStatusDrawerswitch onidentity.status; state cards take genericsubmittedAt/reviewedAt/actionMessage/rejectLabelsprops.KycProviderRejection+KycRequiresDocuments+KycNotStarteddeleted (no longer reachable in the provider-blind model).useMultiPhaseKycFlow+useSumsubKycFlowkept (SDK orchestration).useUnifiedKycStatus,useKycStatus,useQrKycGate,useProviderRejectionStatus,useRailStatusTracking,useBridgeTransferReadiness,useIdentityVerification. ~1470 LoC net removed.IUserProfile.User—bridgeKycStatus,bridgeKyc{StartedAt,ApprovedAt,RejectedAt,RejectionReasonString},kycVerifications. The current user is fully provider-blind. (Kept:IUserRail,IUserKycVerificationtype def,Contact.bridgeKycStatus— counterparty in a different shape.)CounterpartyUser=User & { canReceiveBankOfframp: boolean }(no raw KYC).BankFlowManagerguest branch +useDetermineBank{Claim,Request}Typemigrated.ClaimBankFlowContext.senderKycStatusdropped (orphaned context state, no consumers).Tests + verification
src/lib/content.test.tscontent-submodule failure, identical to base — not ours).git grep -iE "bridgeKyc|kycVerifications" src/— zero current-user reads; remaining hits areContact.bridgeKycStatus, kept type defs, capability-derived variable names likeisUserBridgeKycApproved = hasEnabledRail('bridge'), comments, and the legacydev/debugwhoami./get-userreturns{capabilities, identityVerification};/users/:userIdreturnscanReceiveBankOfframpwithbridgeKycStatusabsent.Open follow-up flags (not in this PR pair)
kyc.consts.isUserKycVerifiedis 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.mantecaUserIddegrades toundefined— was an internal support-dashboard convenience link from the removeduser.kycVerifications.providerUserId. Not a gate, not user-facing.getRegionAccessusesisKycApproved(any enabled rail ⇒ identity cleared at least once) as the Sumsub-approved proxy — faithful for every case the old hook actually hit.Notes
useMultiPhaseKycFlow+useSumsubKycFlowkept — the WebSDK lifecycle plumbing. Only the state-machine hooks went.src/types/capabilities.tswill be replaced bypnpm gen:apionce Feat/request link preview #897's regeneratedopenapi.jsonis consumed.🤖 drafted + reviewed + verified overnight. The agent's status-rewrite PRs (
857612261PART A,c7fabc594PART B) were reviewed; one real regression (resumeregionIntentregression) caught + fixed in #897 (74a01a16).