Skip to content

fix: kyc 2.0 bugs#1693

Merged
kushagrasarathe merged 17 commits intodevfrom
fix/kyc2.0-bugs-v2
Mar 2, 2026
Merged

fix: kyc 2.0 bugs#1693
kushagrasarathe merged 17 commits intodevfrom
fix/kyc2.0-bugs-v2

Conversation

@kushagrasarathe
Copy link
Contributor

@kushagrasarathe kushagrasarathe commented Feb 26, 2026

Summary

bug fixes and improvements for KYC 2.0. Addresses legacy status checks, drawer/SDK issues, rejection UX, and region unlock logic.

Legacy bridgeKycStatus migration

  • Replaced direct bridgeKycStatus === 'approved' checks across 12 files with unified isUserKycVerified() utility that checks all KYC providers (Bridge, Manteca, Sumsub)
  • Limits display now derives from rails-based /users/limits endpoint instead of direct KYC status checks

Region unlock logic

  • Regions now require provider rails to be in a functional state (ENABLED/REQUIRES_INFORMATION/REQUIRES_EXTRA_INFORMATION) before showing as unlocked
  • Prevents showing EU/US/MX as unlocked when Bridge provider submission failed
  • Optimized isBridgeSupportedCountry to use O(1) lookups

KYC drawer & SDK fixes

  • Fixed SDK unresponsiveness — drawer (z-50) was blocking Sumsub SDK Dialog.Panel (z-10). Drawer now closes before opening SDK
  • Extracted closeAndStartKyc helper to deduplicate 3 close+start patterns
  • Fixed rejected state UI: hidden RejectLabelsList in terminal rejection, fixed bottom border when "Rejected on" is the only card row
  • Aligned status labels between activity list and drawer (Processing, Completed, Failed)

DRY refactor

  • Extracted KycFailedContent shared component used by both drawer state and modal
  • Renamed KycRejectedModalKycFailedModal for naming consistency
  • Fixed terminal rejection bug in modal (was showing "try again" copy)

Sumsub reject labels

  • Expanded REJECT_LABEL_MAP from 14 to 65+ entries covering all Sumsub reject/resubmission labels
  • Expanded TERMINAL_REJECT_LABELS from 3 to 15 entries
  • Previously most rejections showed generic fallback text

Other fixes

  • Bridge ToS stuck issue
  • Removed email from add bank account flow
  • Handle pending Bridge ToS via home carousel card
  • Handle retry if user abandons KYC
  • Removed duplicate rails entry in KYC drawer
  • Stop auto-initiate KYC in deposit flow
  • Sumsub SDK unmounting issue

Files changed (39 files)

Area Files What changed
Legacy migration 12 files (Claim, PostSignup, Profile, Send, Request, Points, hooks, utils) bridgeKycStatusisUserKycVerified()
Limits 3 files (useLimitsValidation, BridgeLimitsView, LimitsPageView) isUserMantecaKycApprovedhasMantecaLimits
Region unlock useIdentityVerification.tsx hasProviderAccess() check, O(1) country lookup
KYC drawer KycStatusDrawer, KycStatusItem, KycFailed, CountryRegionRow SDK z-index fix, status labels, border fix
DRY refactor KycFailedContent (new), KycFailedModal (renamed), RegionsVerification Shared rejection content, naming consistency
Reject labels sumsub-reject-labels.consts.ts 14 → 65+ mapped labels, 3 → 15 terminal labels
Bridge ToS BridgeTosStep, HomeCarouselCTA, useHomeCarouselCTAs ToS stuck fix, carousel reminder

Test plan

  • Complete KYC via Sumsub for Bridge region → verify regions unlock correctly
  • Complete KYC via Sumsub for LATAM region → verify LATAM unlocks, others don't
  • Trigger rejected KYC → verify user-friendly reject reason shows (not generic fallback)
  • Trigger terminal rejection → verify "Contact support" CTA, no "try again" copy
  • Open KYC drawer → click retry → verify Sumsub SDK opens and is interactive
  • Check activity list KYC status labels match drawer labels (Processing/Completed/Failed)
  • Verify limits page shows correctly for Manteca-verified users
  • Check profile verified badge shows for users verified through any provider
  • Verify Bridge ToS carousel card appears when ToS is pending

@vercel
Copy link

vercel bot commented Feb 26, 2026

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Mar 2, 2026 5:59pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

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

Replace multi-step Bridge ToS confirmation and manual polling with a single helper that confirms ToS and awaits rails readiness; add KycNotStarted and keep-drawer-mounted behavior; remove BridgeTosReminder; integrate BridgeTosStep into Home CTA; adjust Add/Withdraw KYC/email handling and minor API body change.

Changes

Cohort / File(s) Summary
Bridge ToS helper & integration
src/hooks/useMultiPhaseKycFlow.ts, src/components/Kyc/BridgeTosStep.tsx
Add confirmBridgeTosAndAwaitRails(fetchUser) (confirm ToS with retry, poll fetchUser up to 3 times); BridgeTosStep now uses this helper and sets modal phase to preparing before awaiting completion.
KYC drawer & persistent SDK mount
src/components/Kyc/KycStatusDrawer.tsx, src/components/Kyc/KycStatusItem.tsx, src/components/Kyc/states/KycNotStarted.tsx
Introduce KycNotStarted, add onKeepMounted prop to drawer, allow drawer to remain mounted during SDK flows, consolidate close/resume handlers to keep component mounted while SDK runs.
KYC completed state cleanup
src/components/Kyc/states/KycCompleted.tsx
Remove rails prop and rails-related UI/filtering from KycCompleted; signature updated to drop rails.
Home CTA / carousel integration
src/components/Home/HomeCarouselCTA/index.tsx, src/hooks/useHomeCarouselCTAs.tsx
Add bridge-tos CTA and showBridgeTos/setShowBridgeTos state; HomeCarouselCTA renders BridgeTosStep and calls fetchUser after completion; hook API extended to return show/set pair.
Removed Bridge ToS reminder & HomeHistory cleanup
src/components/Kyc/BridgeTosReminder.tsx, src/components/Home/HomeHistory.tsx
Remove BridgeTosReminder component and its imports/usages; remove reminder render paths in HomeHistory.
Add/Withdraw flows and forms
src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/AddWithdraw/DynamicBankAccountForm.tsx
AddWithdrawCountriesList: re-fetch fetchUser() to get fresh KYC/accounts, remove email-on-load shortcut, rely on KYC verification. DynamicBankAccountForm: stop auto-rendering Email input in non-claim flow when email missing.
Mobile add-money bank page
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
Remove kyc URL step from BridgeBankStep type; convert URL-driven KYC step into modal-based InitiateKycModal; remove automatic KYC page start/redirect.
KYC initiation modal sequencing
src/app/(mobile-ui)/withdraw/manteca/page.tsx, src/components/AddMoney/components/MantecaAddMoney.tsx, src/components/Claim/Link/MantecaFlowManager.tsx
Change sequence to await sumsubFlow.handleInitiateKyc('LATAM') before closing InitiateKycModal (close post-initiation instead of pre-initiation).
KYC verification helper & replacements
src/constants/kyc.consts.ts, multiple callers...
Add isUserKycVerified(user) and replace many direct bridgeKycStatus === 'approved' checks across components/hooks to use the helper (Contacts, Claim flows, Profile, request views, utils).
Sumsub reject labels data
src/constants/sumsub-reject-labels.consts.ts
Massively expand REJECT_LABEL_MAP with many new reject labels and extend TERMINAL_REJECT_LABELS set to include additional terminal reasons.
Kyc failure UI refactor
src/components/Kyc/KycFailedContent.tsx, src/components/Kyc/modals/KycFailedModal.tsx, src/components/Kyc/states/KycFailed.tsx
Add KycFailedContent, rename/refactor modal from KycRejectedModalKycFailedModal, and replace RejectLabelsList usage with KycFailedContent; simplify modal icon/description handling.
Limits & rails-driven gating
src/features/limits/hooks/useLimitsValidation.ts, src/features/limits/views/*
Shift validation and user-type flags from KYC-based checks to presence of rails/limits (hasMantecaLimits/hasBridgeLimits); update hooks/views to consume/return hasMantecaLimits.
Country/region & misc props
src/components/Kyc/CountryRegionRow.tsx, src/components/Kyc/states/KycCompleted.tsx
Add optional hideBottomBorder prop to CountryRegionRow; remove rails prop from KycCompleted (see above).
API call body change
src/app/actions/users.ts
confirmBridgeTos POST now includes body: JSON.stringify({}) (send empty JSON body).
Misc. small updates
multiple files (HomeHistory, HomeCarouselCTA, profile views, claim flows, utils, hooks...)
Various replacements to use isUserKycVerified and other refactors (minor rendering/text updates, subtitle text changes, imports).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Hugo0
  • jjramirezn
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: kyc 2.0 bugs' directly addresses the main focus of the changeset, which involves comprehensive KYC 2.0 bug fixes and refactors across multiple files.
Description check ✅ Passed The description is well-structured and thoroughly related to the changeset, detailing legacy migration, region unlock logic, KYC drawer fixes, DRY refactors, reject labels expansion, and Bridge ToS improvements with a comprehensive test plan.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/kyc2.0-bugs-v2

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/Kyc/BridgeTosStep.tsx (1)

61-73: Consider showing loading state during ToS confirmation polling.

Unlike handleTosIframeClose in useMultiPhaseKycFlow.ts (which sets modalPhase='preparing' before awaiting), this handler provides no visual feedback during the polling period after the iframe closes. The user may experience a blank or unresponsive UI for up to ~6 seconds.

🔧 Suggested improvement
 const handleIframeClose = useCallback(
     async (source?: 'manual' | 'completed' | 'tos_accepted') => {
         setShowIframe(false)

         if (source === 'tos_accepted') {
+            setIsLoading(true)
             await confirmBridgeTosAndAwaitRails(fetchUser)
+            setIsLoading(false)
             onComplete()
         } else {
             onSkip()
         }
     },
     [fetchUser, onComplete, onSkip]
 )

You'd then need to render a loading indicator when isLoading && !showIframe && !error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/BridgeTosStep.tsx` around lines 61 - 73, The handler
handleIframeClose currently hides the iframe then awaits
confirmBridgeTosAndAwaitRails without setting a loading phase, causing a blank
UI; before awaiting confirmBridgeTosAndAwaitRails set the modalPhase (or an
isLoading flag) to the preparing/loading state (e.g., modalPhase='preparing' or
setIsLoading(true)) and after the await reset it (modalPhase='' or
setIsLoading(false)); also ensure the component renders a loading indicator when
isLoading && !showIframe && !error so users see progress while
confirmBridgeTosAndAwaitRails runs; use the existing state setters
(setShowIframe, modalPhase setter or setIsLoading) and props (fetchUser,
onComplete, onSkip) referenced in handleIframeClose to implement this.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/Kyc/BridgeTosStep.tsx`:
- Around line 61-73: The handler handleIframeClose currently hides the iframe
then awaits confirmBridgeTosAndAwaitRails without setting a loading phase,
causing a blank UI; before awaiting confirmBridgeTosAndAwaitRails set the
modalPhase (or an isLoading flag) to the preparing/loading state (e.g.,
modalPhase='preparing' or setIsLoading(true)) and after the await reset it
(modalPhase='' or setIsLoading(false)); also ensure the component renders a
loading indicator when isLoading && !showIframe && !error so users see progress
while confirmBridgeTosAndAwaitRails runs; use the existing state setters
(setShowIframe, modalPhase setter or setIsLoading) and props (fetchUser,
onComplete, onSkip) referenced in handleIframeClose to implement this.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4587440 and e0f4d20.

📒 Files selected for processing (3)
  • src/components/Kyc/BridgeTosStep.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/hooks/useMultiPhaseKycFlow.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Around line 103-111: You fetched a fresh user snapshot (freshUser) to
determine KYC status but still build currentAccountIds from the possibly stale
context user (user?.accounts), which can pick the wrong account after an add;
change the account-diffing logic to use freshUser.accounts (or fallback to
user.accounts only if freshUser is undefined) when constructing
currentAccountIds and any subsequent account set/diff operations (see fetchUser,
freshUser, currentKycStatus, isUserKycVerified, and user?.accounts) so the
selection uses the same fresh snapshot used for KYC.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0f4d20 and 54d0aa4.

📒 Files selected for processing (3)
  • src/app/actions/users.ts
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/AddWithdraw/DynamicBankAccountForm.tsx
💤 Files with no reviewable changes (1)
  • src/components/AddWithdraw/DynamicBankAccountForm.tsx

@coderabbitai coderabbitai bot added the enhancement New feature or request label Feb 27, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/components/Home/HomeCarouselCTA/index.tsx (1)

93-98: Consider removing redundant fetchUser() call.

BridgeTosStep.handleIframeClose already invokes confirmBridgeTosAndAwaitRails(fetchUser) before calling onComplete(). The additional await fetchUser() here results in a duplicate API request.

If the intent is to ensure fresh user data post-ToS, the call inside confirmBridgeTosAndAwaitRails already handles that. Removing it avoids an unnecessary network round-trip.

♻️ Proposed fix
 const handleTosComplete = useCallback(async () => {
     setShowBridgeTos(false)
     setCarouselCTAs((prev) => prev.filter((c) => c.id !== 'bridge-tos'))
-    await fetchUser()
-}, [setShowBridgeTos, setCarouselCTAs, fetchUser])
+}, [setShowBridgeTos, setCarouselCTAs])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Home/HomeCarouselCTA/index.tsx` around lines 93 - 98,
handleTosComplete is calling await fetchUser() redundantly because
BridgeTosStep.handleIframeClose already calls
confirmBridgeTosAndAwaitRails(fetchUser) which refreshes user data; remove the
final await fetchUser() from handleTosComplete and keep the existing
setShowBridgeTos(false) and setCarouselCTAs(...) behavior so you don't trigger a
duplicate network request while preserving state updates.
src/components/Kyc/states/KycNotStarted.tsx (1)

16-18: Make the primary CTA variant explicit.

Please set variant="purple" explicitly so this button remains compliant even if Button defaults change.

♻️ Proposed change
-            <Button className="w-full" shadowSize="4" onClick={onResume} disabled={isLoading}>
+            <Button variant="purple" className="w-full" shadowSize="4" onClick={onResume} disabled={isLoading}>
                 {isLoading ? 'Loading...' : 'Continue verification'}
             </Button>

As per coding guidelines "Primary CTA styling: use <Button variant='purple' shadowSize='4' className='w-full'> without size prop".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/states/KycNotStarted.tsx` around lines 16 - 18, The
primary CTA Button in KycNotStarted.tsx should explicitly set the variant to
"purple" to prevent style regressions; update the Button element (the one using
onResume and isLoading) to include variant="purple" and ensure it does not
include a size prop, keeping shadowSize="4" and className="w-full" as-is so the
CTA matches the Primary CTA styling guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/Home/HomeCarouselCTA/index.tsx`:
- Around line 93-98: handleTosComplete is calling await fetchUser() redundantly
because BridgeTosStep.handleIframeClose already calls
confirmBridgeTosAndAwaitRails(fetchUser) which refreshes user data; remove the
final await fetchUser() from handleTosComplete and keep the existing
setShowBridgeTos(false) and setCarouselCTAs(...) behavior so you don't trigger a
duplicate network request while preserving state updates.

In `@src/components/Kyc/states/KycNotStarted.tsx`:
- Around line 16-18: The primary CTA Button in KycNotStarted.tsx should
explicitly set the variant to "purple" to prevent style regressions; update the
Button element (the one using onResume and isLoading) to include
variant="purple" and ensure it does not include a size prop, keeping
shadowSize="4" and className="w-full" as-is so the CTA matches the Primary CTA
styling guideline.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54d0aa4 and 1eb1658.

📒 Files selected for processing (7)
  • src/components/Home/HomeCarouselCTA/index.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Kyc/BridgeTosReminder.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/states/KycNotStarted.tsx
  • src/hooks/useHomeCarouselCTAs.tsx
💤 Files with no reviewable changes (2)
  • src/components/Home/HomeHistory.tsx
  • src/components/Kyc/BridgeTosReminder.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 389-397: The modal is being closed before awaiting
sumsubFlow.handleInitiateKyc, hiding the isLoading state; change the onVerify
handler in InitiateKycModal so it calls await
sumsubFlow.handleInitiateKyc('STANDARD') first and only calls
setShowKycModal(false) after the promise resolves (or in a success
branch/finally if you want to close on both success/failure). Ensure the handler
still preserves sumsubFlow.isLoading as the source of truth for the modal
loading UI (use InitiateKycModal's isLoading prop) and reference the existing
InitiateKycModal, setShowKycModal, and sumsubFlow.handleInitiateKyc symbols when
making the change.
- Around line 157-163: The useEffect that bootstraps the step is blocked by the
"user === null" guard—remove that guard so the effect sets setUrlState({ step:
'inputAmount' }) whenever urlState.step is falsy; i.e., in the useEffect (the
hook bootstrapping step in page.tsx) keep the early return for if
(urlState.step) but delete or stop checking user === null so the effect can run
and avoid the loader deadlock.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1eb1658 and 72d75f0.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/states/KycCompleted.tsx
💤 Files with no reviewable changes (1)
  • src/components/Kyc/states/KycCompleted.tsx

…fied

Added isUserKycVerified utility in kyc.consts.ts that checks all KYC
providers (Bridge, Manteca, Sumsub) instead of only bridge status.
Replaced direct bridgeKycStatus === 'approved' checks across 12 files
to support KYC 2.0 where users may be verified through any provider.
Replaced legacy isUserMantecaKycApproved with hasMantecaLimits from
useLimits hook in BridgeLimitsView and LimitsPageView. Limits now
derive from the rails-based /users/limits endpoint instead of direct
KYC status checks.
Regions now require provider rails to be in a functional state
(ENABLED/REQUIRES_INFORMATION/REQUIRES_EXTRA_INFORMATION) before
showing as unlocked. Prevents showing EU/US/MX as unlocked when
Bridge submission failed. Also optimized isBridgeSupportedCountry
to use O(1) lookups instead of allocating arrays per call.
- Fixed SDK unresponsiveness by closing drawer before opening Sumsub SDK
  (drawer z-50 was blocking SDK Dialog.Panel at z-10)
- Extracted closeAndStartKyc helper to deduplicate 3 close+start patterns
- Aligned status labels between activity list and drawer (Processing,
  Completed, Failed)
- Fixed rejected state: hide RejectLabelsList in terminal rejection,
  fixed bottom border when Rejected On is the only card row
- Extracted KycFailedContent shared component for drawer and modal
- Renamed KycRejectedModal to KycFailedModal for naming consistency
…ages

Added 50+ reject labels from Sumsub docs covering data/address issues,
document quality, selfie/liveness, fraud/forgery, and compliance cases.
Previously only 14 labels were mapped, causing most rejections to show
a generic fallback. Also expanded TERMINAL_REJECT_LABELS from 3 to 15
entries for accurate terminal rejection detection.
# Conflicts:
#	src/app/(mobile-ui)/points/invites/page.tsx
#	src/app/(mobile-ui)/points/page.tsx
@kushagrasarathe
Copy link
Contributor Author

@coderabbitai review in depth.

Evaluate:

  • risk of introducing new bugs and breaking existing functionality
  • performance impact and latency
  • code quality and DRY

Also, write a very concise testing guide for QA

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

@kushagrasarathe, I'll provide an in-depth analysis of this PR:

🚨 Risk Assessment: HIGH-MEDIUM

Critical Risk Areas

1. ToS Polling Logic (useMultiPhaseKycFlow.ts)

export async function confirmBridgeTosAndAwaitRails(fetchUser: () => Promise<any>) {
    // Hard-coded delays and retries
    await new Promise((resolve) => setTimeout(resolve, 2000))
    for (let i = 0; i < 3; i++) {
        // Polls only 3 times with 2s intervals
  • Risk: Fixed retry count (3) and delays (2s) may be insufficient for slow backend responses
  • Impact: Users could complete ToS but UI shows incomplete state
  • Mitigation needed: Add timeout configuration and better error handling

2. Email Collection Removal

  • DynamicBankAccountForm.tsx: Removed email input for non-claim flows
  • AddWithdrawCountriesList.tsx: Removed hasEmailOnLoad check
  • Risk: Email might not be collected before it's needed elsewhere
  • Impact: Could break flows that depend on email (notifications, verification)
  • Question: Is Sumsub guaranteed to collect email in all cases?

3. State Synchronization Issues

// KycStatusItem.tsx
const [keepDrawerMounted, setKeepDrawerMounted] = useState(false)
// Drawer renders when isDrawerOpen OR keepDrawerMounted
{(isDrawerOpen || keepDrawerMounted) && (
  • Risk: Drawer could remain mounted indefinitely if state isn't properly cleared
  • Impact: Memory leaks, hidden modals blocking interactions
  • Mitigation: Needs cleanup on unmount

4. Modal Timing Race Conditions
In manteca flows, changed from:

// Before: close then initiate
setShowKycModal(false)
await sumsubFlow.handleInitiateKyc('LATAM')

// After: initiate then close
await sumsubFlow.handleInitiateKyc('LATAM')
setShowKycModal(false)
  • Risk: If handleInitiateKyc throws, modal stays open forever
  • Impact: User stuck on modal, can't proceed
  • Needs: try-catch with finally block to ensure modal closes

5. Breaking Navigation Pattern

  • bank/page.tsx: Removed 'kyc' from URL steps
  • Risk: Deep links with ?step=kyc will break
  • Impact: Existing bookmarks, shared links, external integrations fail
  • Mitigation: Add redirect/fallback for old URLs

⚡ Performance & Latency Impact: MEDIUM

Added Latency

  1. ToS Flow: Minimum 6 seconds added (2s initial + 3x 2s polls)
  2. Multiple fetchUser calls:
    • After ToS completion
    • In AddWithdrawCountriesList
    • After KYC completion
  3. Component Persistence: keepDrawerMounted keeps KycStatusDrawer + SumsubKycModals in DOM

Optimization Concerns

  • No request deduplication for concurrent fetchUser calls
  • Polling could use exponential backoff instead of fixed intervals
  • Consider WebSocket or server-sent events for status updates instead of polling

🎨 Code Quality & DRY: MIXED

✅ Improvements

  1. Consolidated ToS logic: confirmBridgeTosAndAwaitRails centralizes previously scattered code
  2. Removed duplication: BridgeTosReminder removed from multiple locations
  3. Better separation: Modal-based KYC vs URL-driven flow

⚠️ Concerns

  1. Magic Numbers: Hard-coded 2000, 3 in polling logic
  2. Complex Conditional Logic:
    // KycStatusDrawer.tsx - nested conditions hard to follow
    isBridgeKyc || region === 'STANDARD'
  3. Scattered Modal Management: ToS modals now in multiple places (HomeCarouselCTA, bank/page.tsx)
  4. Inconsistent Error Handling: Some flows lack try-catch, others handle errors differently
  5. Missing Type Safety: fetchUser: () => Promise<any> - should be typed

Recommendations

// Create constants file
export const KYC_POLLING = {
  INITIAL_DELAY_MS: 2000,
  POLL_INTERVAL_MS: 2000,
  MAX_RETRIES: 3,
} as const;

// Add proper typing
export async function confirmBridgeTosAndAwaitRails(
  fetchUser: () => Promise<{ user: User; rails: IUserRail[] }>
): Promise<void>

// Add error boundary
try {
  await sumsubFlow.handleInitiateKyc('LATAM')
} catch (error) {
  console.error('KYC initiation failed:', error)
} finally {
  setShowKycModal(false)
}

📋 QA Testing Guide

Critical Paths to Test

1. Bridge ToS Flow

  • Click "Accept Terms" in home carousel CTA
  • Complete ToS acceptance
  • Verify carousel CTA disappears
  • Verify user profile shows ToS accepted
  • Test: Wait >6 seconds, ensure flow completes
  • Test: Network failure during ToS - verify error handling

2. KYC Not Started State

  • Start KYC but close modal without submitting
  • Open KYC status from profile
  • Verify shows "Not completed" with Resume button
  • Click Resume - verify returns to Sumsub flow
  • Complete KYC - verify drawer closes properly

3. Bank Account Flow (Critical Change)

  • Navigate to /add-money/[country]/bank
  • Test without KYC: should show InitiateKycModal
  • Complete KYC via modal
  • Verify returns to amount input (not KYC page)
  • Test old URL ?step=kyc - ensure doesn't break

4. Email Collection

  • Add money without existing email
  • Verify email collected by Sumsub
  • Check user profile has email after KYC
  • Test withdrawal flow - ensure email available

5. Manteca Flows

  • Withdraw via manteca (verify modal)
  • Start KYC, cancel mid-flow
  • Verify modal closes properly
  • Complete KYC - ensure no stuck modals

6. Drawer Mount/Unmount

  • Open KYC status drawer
  • Start KYC (drawer should stay mounted)
  • Close drawer (component should persist)
  • Complete or cancel KYC
  • Verify drawer unmounts after completion

7. Regression Tests

  • All existing KYC flows still work
  • Profile page KYC status correct
  • Completed KYC shows correct info (no rails display)
  • Deep links still functional
  • Browser back/forward navigation works

Edge Cases

  • Network timeout during ToS polling
  • Rapid clicking ToS accept button
  • Multiple browser tabs open during KYC
  • KYC abandoned at different stages
  • Page refresh mid-flow

Performance Checks

  • Monitor fetchUser call frequency (shouldn't be excessive)
  • Check for memory leaks (keepDrawerMounted cleanup)
  • Verify no unnecessary re-renders
  • Test on slow network (3G throttling)

Overall Assessment: This refactor improves code organization but introduces several high-risk changes. Recommend thorough testing of ToS polling, modal state management, and email collection before merging.


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: CR
Repo: peanutprotocol/peanut-ui PR: 0
File: .cursorrules:0-0
Timestamp: 2026-02-26T21:54:25.278Z
Learning: Applies to docs/PR.md : Maintain a concise docs/PR.md during PR work with: 1) Summary of changes, 2) Risks (what might break), 3) QA guidelines (what to test)

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1654
File: src/components/Card/CardGeoScreen.tsx:41-45
Timestamp: 2026-02-02T06:03:48.192Z
Learning: In peanutprotocol/peanut-ui, KYC 2.0 uses the unified entry route '/profile/identity-verification'. Components (e.g., src/components/Card/CardGeoScreen.tsx) should avoid region-specific paths like '/profile/identity-verification/europe/bridge' and navigate to the unified route instead.

Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1094
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:279-279
Timestamp: 2025-08-14T08:02:26.705Z
Learning: For hotfixes in the peanut-ui codebase, prefer generic error messages over specific validation error details until the copy can be reviewed with the team, even when the validation functions return detailed error messages.
✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
src/components/Kyc/BridgeTosStep.tsx (1)

61-73: ⚠️ Potential issue | 🟡 Minor

Add error handling for ToS confirmation failure.

If confirmBridgeTosAndAwaitRails throws, the iframe is already closed but onComplete() won't be called. The user could be left in an inconsistent state where the modal dismissed but the flow didn't complete. Consider wrapping in try/catch to ensure either onComplete() or onSkip() is called.

🛡️ Proposed fix
     const handleIframeClose = useCallback(
         async (source?: 'manual' | 'completed' | 'tos_accepted') => {
             setShowIframe(false)
 
             if (source === 'tos_accepted') {
-                await confirmBridgeTosAndAwaitRails(fetchUser)
-                onComplete()
+                try {
+                    await confirmBridgeTosAndAwaitRails(fetchUser)
+                    onComplete()
+                } catch (error) {
+                    console.error('Failed to confirm ToS:', error)
+                    // Still complete the flow - the ToS was accepted in the iframe,
+                    // backend will eventually sync via rail status tracking
+                    onComplete()
+                }
             } else {
                 onSkip()
             }
         },
         [fetchUser, onComplete, onSkip]
     )

Based on learnings: "The phase-transition effect (which monitors rail status via useRailStatusTracking) serves as the actual validation mechanism, making the flow resilient to transient confirmation API failures while still ensuring correct state progression."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/BridgeTosStep.tsx` around lines 61 - 73, The
handleIframeClose callback should guard the confirmBridgeTosAndAwaitRails call
so failures don't leave the UI inconsistent: wrap the await
confirmBridgeTosAndAwaitRails(fetchUser) call in try/catch inside
handleIframeClose (keep setShowIframe(false) before the try), call onComplete()
when the confirmation succeeds, and call onSkip() in the catch block (optionally
logging the error) to ensure one of the two callbacks always runs; reference
functions/values: handleIframeClose, confirmBridgeTosAndAwaitRails, fetchUser,
onComplete, onSkip, and setShowIframe.
🧹 Nitpick comments (3)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

104-110: Avoid semantic collision with the shared isUserKycVerified predicate.

Line 105 names a bridge-only check as isUserKycVerified, which can drift from the shared app-level KYC predicate and confuse future changes. Prefer a bridge-specific name and nullish coalescing in Line 104.

♻️ Suggested cleanup
-        const currentKycStatus = freshUser?.user?.bridgeKycStatus || liveKycStatus || user?.user.bridgeKycStatus
-        const isUserKycVerified = currentKycStatus === 'approved'
+        const currentKycStatus =
+            freshUser?.user?.bridgeKycStatus ?? liveKycStatus ?? user?.user.bridgeKycStatus
+        const isBridgeKycApproved = currentKycStatus === 'approved'

-        if (isUserKycVerified) {
+        if (isBridgeKycApproved) {
             const currentAccountIds = new Set((freshUser?.accounts ?? user?.accounts ?? []).map((acc) => acc.id))
             ...
         }

-        if (!isUserKycVerified) {
+        if (!isBridgeKycApproved) {
             await sumsubFlow.handleInitiateKyc('STANDARD')
         }

As per coding guidelines "Follow DRY (Don't Repeat Yourself) - reuse existing code and abstract shared functionality; use shared consts from src/constants".

Also applies to: 152-153

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx` around lines 104 -
110, Rename the bridge-specific predicate and tighten the nullish logic: replace
currentKycStatus and isUserKycVerified usage with a bridge-specific name (e.g.,
currentBridgeKycStatus and isBridgeKycApproved) and compute it using nullish
coalescing so it reads const currentBridgeKycStatus =
freshUser?.user?.bridgeKycStatus ?? liveKycStatus ??
user?.user?.bridgeKycStatus; const isBridgeKycApproved = currentBridgeKycStatus
=== 'approved'; update all references (including the block that uses
currentAccountIds and the similar usage around the other occurrence) to use
these new identifiers to avoid colliding with any shared app-level KYC
predicate.
src/components/Home/HomeCarouselCTA/index.tsx (1)

94-99: Avoid duplicate user refresh after Bridge ToS completion.

BridgeTosStep already awaits confirmBridgeTosAndAwaitRails(fetchUser) before calling onComplete, so the extra await fetchUser() here adds another round-trip with little upside.

♻️ Suggested simplification
-    const { user, fetchUser } = useAuth()
+    const { user } = useAuth()
@@
-    const handleTosComplete = useCallback(async () => {
+    const handleTosComplete = useCallback(() => {
         setShowBridgeTos(false)
         setCarouselCTAs((prev) => prev.filter((c) => c.id !== 'bridge-tos'))
-        await fetchUser()
-    }, [setShowBridgeTos, setCarouselCTAs, fetchUser])
+    }, [setShowBridgeTos, setCarouselCTAs])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Home/HomeCarouselCTA/index.tsx` around lines 94 - 99,
handleTosComplete currently calls await fetchUser() even though BridgeTosStep
already awaits confirmBridgeTosAndAwaitRails(fetchUser) before invoking
onComplete, causing a duplicate user refresh; remove the redundant await
fetchUser() from handleTosComplete and keep only the existing state updates
(setShowBridgeTos(false) and setCarouselCTAs(...)) so the user is refreshed once
via confirmBridgeTosAndAwaitRails; ensure you update the handleTosComplete
function signature and dependency array (useCallback) to remove the direct
fetchUser dependency if no longer used.
src/features/limits/views/LimitsPageView.tsx (1)

68-68: Rename hasMantecaKyc to reflect limits-based semantics.

At Line 68, the value represents limits availability (hasMantecaLimits), not KYC approval. Keeping the old prop name makes downstream routing logic easier to misinterpret.

♻️ Suggested rename
-interface UnlockedRegionsListProps {
+interface UnlockedRegionsListProps {
     regions: Region[]
-    hasMantecaKyc: boolean
+    hasMantecaLimits: boolean
 }
 
-const UnlockedRegionsList = ({ regions, hasMantecaKyc }: UnlockedRegionsListProps) => {
+const UnlockedRegionsList = ({ regions, hasMantecaLimits }: UnlockedRegionsListProps) => {
@@
-                        const route = getProviderRoute(region.path, hasMantecaKyc)
+                        const route = getProviderRoute(region.path, hasMantecaLimits)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/limits/views/LimitsPageView.tsx` at line 68, The prop name
hasMantecaKyc on the UnlockedRegionsList is misleading and should be renamed to
hasMantecaLimits; update the usage at UnlockedRegionsList in LimitsPageView
(change hasMantecaKyc to hasMantecaLimits), rename the prop in the
UnlockedRegionsList component signature/props interface, and update all other
references/consumers (types, tests, and any routing/logic that reads this prop)
to use hasMantecaLimits so the semantics clearly reflect limits availability
rather than KYC.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 206-207: Replace the ad-hoc KYC check that sets currentKycStatus
and computes isUserKycVerified via direct equality against 'approved' (using
liveKycStatus and user?.user.bridgeKycStatus) with the project's shared KYC
verifier: import the exported verifier from the shared KYC module and call it
(passing kycVerifications plus the existing liveKycStatus/bridgeKycStatus
inputs) to compute isUserKycVerified, removing the manual currentKycStatus
equality check and relying on the canonical helper instead.

In `@src/components/Kyc/KycStatusDrawer.tsx`:
- Around line 63-71: The closeAndStartKyc callback currently calls
onKeepMounted?.(true) then awaits sumsubFlow.handleInitiateKyc, which can leave
the component stuck mounted if that promise rejects; modify closeAndStartKyc to
wrap the await in try/catch/finally: in try await
sumsubFlow.handleInitiateKyc(regionIntent, levelName), in catch log/handle the
error (or rethrow) and ensure any error path triggers onKeepMounted?.(false) if
the flow did not complete, and in finally ensure proper state cleanup so
handleFlowDone's cleanup is not the only path that calls onKeepMounted?.(false);
reference closeAndStartKyc, sumsubFlow.handleInitiateKyc, onKeepMounted,
onClose, and handleFlowDone when making the change.

In `@src/constants/kyc.consts.ts`:
- Around line 49-50: Replace the hard-coded string check for bridgeKycStatus
with the shared matcher: call isKycStatusApproved(user.bridgeKycStatus) instead
of comparing to 'approved', and keep the existing fallback that returns
user.kycVerifications?.some((v) => isKycStatusApproved(v.status)) ?? false so
both bridgeKycStatus and KYC verification entries use the same approval logic.

In `@src/hooks/useDetermineBankClaimType.ts`:
- Around line 29-32: The useEffect inside useDetermineBankClaimType references
isUserKycApproved (used in determineBankClaimType) but does not include it in
the effect dependency array, causing claimType to become stale; update the
dependency list for that useEffect to include isUserKycApproved so the effect
reruns when the current user's KYC status changes (locate the
determineBankClaimType async function and the useEffect that sets claimType and
add isUserKycApproved to its dependencies).

In `@src/hooks/useDetermineBankRequestType.ts`:
- Around line 29-31: The useEffect that declares determineBankRequestType reads
isUserKycApproved but does not include it in its dependency array, causing
requestType to become stale when KYC changes; update the dependency array of the
useEffect that contains determineBankRequestType to include isUserKycApproved so
the effect re-runs on KYC state changes (locate the effect using the useEffect
declaration and the determineBankRequestType async function, and ensure
requestType is recalculated when isUserKycApproved changes).

In `@src/hooks/useMultiPhaseKycFlow.ts`:
- Around line 15-30: The exported helper confirmBridgeTosAndAwaitRails in the
hook contains hard-coded polling values; move this function into a new utility
module (e.g., utils file) and stop exporting it from the hook; update all call
sites to import confirmBridgeTosAndAwaitRails from the new utils module. Replace
the literal 2000 and 3 with shared constants (e.g., POLL_INTERVAL_MS and
POLL_RETRIES) imported from the shared constants module, and keep the existing
confirmBridgeTos and fetchUser references unchanged inside the function. Ensure
the original hook no longer exports the helper and that any tests or usages are
updated to import the utility from its new location.

---

Outside diff comments:
In `@src/components/Kyc/BridgeTosStep.tsx`:
- Around line 61-73: The handleIframeClose callback should guard the
confirmBridgeTosAndAwaitRails call so failures don't leave the UI inconsistent:
wrap the await confirmBridgeTosAndAwaitRails(fetchUser) call in try/catch inside
handleIframeClose (keep setShowIframe(false) before the try), call onComplete()
when the confirmation succeeds, and call onSkip() in the catch block (optionally
logging the error) to ensure one of the two callbacks always runs; reference
functions/values: handleIframeClose, confirmBridgeTosAndAwaitRails, fetchUser,
onComplete, onSkip, and setShowIframe.

---

Nitpick comments:
In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Around line 104-110: Rename the bridge-specific predicate and tighten the
nullish logic: replace currentKycStatus and isUserKycVerified usage with a
bridge-specific name (e.g., currentBridgeKycStatus and isBridgeKycApproved) and
compute it using nullish coalescing so it reads const currentBridgeKycStatus =
freshUser?.user?.bridgeKycStatus ?? liveKycStatus ??
user?.user?.bridgeKycStatus; const isBridgeKycApproved = currentBridgeKycStatus
=== 'approved'; update all references (including the block that uses
currentAccountIds and the similar usage around the other occurrence) to use
these new identifiers to avoid colliding with any shared app-level KYC
predicate.

In `@src/components/Home/HomeCarouselCTA/index.tsx`:
- Around line 94-99: handleTosComplete currently calls await fetchUser() even
though BridgeTosStep already awaits confirmBridgeTosAndAwaitRails(fetchUser)
before invoking onComplete, causing a duplicate user refresh; remove the
redundant await fetchUser() from handleTosComplete and keep only the existing
state updates (setShowBridgeTos(false) and setCarouselCTAs(...)) so the user is
refreshed once via confirmBridgeTosAndAwaitRails; ensure you update the
handleTosComplete function signature and dependency array (useCallback) to
remove the direct fetchUser dependency if no longer used.

In `@src/features/limits/views/LimitsPageView.tsx`:
- Line 68: The prop name hasMantecaKyc on the UnlockedRegionsList is misleading
and should be renamed to hasMantecaLimits; update the usage at
UnlockedRegionsList in LimitsPageView (change hasMantecaKyc to
hasMantecaLimits), rename the prop in the UnlockedRegionsList component
signature/props interface, and update all other references/consumers (types,
tests, and any routing/logic that reads this prop) to use hasMantecaLimits so
the semantics clearly reflect limits availability rather than KYC.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b582765 and ac4dacf.

📒 Files selected for processing (39)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/points/invites/page.tsx
  • src/app/(mobile-ui)/points/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/AddWithdraw/DynamicBankAccountForm.tsx
  • src/components/Claim/Claim.tsx
  • src/components/Claim/Link/MantecaFlowManager.tsx
  • src/components/Global/PostSignupActionManager/index.tsx
  • src/components/Home/HomeCarouselCTA/index.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Kyc/BridgeTosReminder.tsx
  • src/components/Kyc/BridgeTosStep.tsx
  • src/components/Kyc/CountryRegionRow.tsx
  • src/components/Kyc/KycFailedContent.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/modals/KycFailedModal.tsx
  • src/components/Kyc/states/KycCompleted.tsx
  • src/components/Kyc/states/KycFailed.tsx
  • src/components/Kyc/states/KycNotStarted.tsx
  • src/components/Profile/components/PublicProfile.tsx
  • src/components/Profile/views/ProfileEdit.view.tsx
  • src/components/Profile/views/RegionsVerification.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/sumsub-reject-labels.consts.ts
  • src/features/limits/hooks/useLimitsValidation.ts
  • src/features/limits/views/BridgeLimitsView.tsx
  • src/features/limits/views/LimitsPageView.tsx
  • src/hooks/useDetermineBankClaimType.ts
  • src/hooks/useDetermineBankRequestType.ts
  • src/hooks/useHomeCarouselCTAs.tsx
  • src/hooks/useIdentityVerification.tsx
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/utils/general.utils.ts
💤 Files with no reviewable changes (4)
  • src/components/AddWithdraw/DynamicBankAccountForm.tsx
  • src/components/Kyc/BridgeTosReminder.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Kyc/states/KycCompleted.tsx

@kushagrasarathe kushagrasarathe merged commit 3cd41fe into dev Mar 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant