Skip to content

chore: prod release 134 fe#1752

Merged
jjramirezn merged 120 commits intomainfrom
dev
Mar 17, 2026
Merged

chore: prod release 134 fe#1752
jjramirezn merged 120 commits intomainfrom
dev

Conversation

@kushagrasarathe
Copy link
Contributor

No description provided.

@vercel
Copy link

vercel bot commented Mar 16, 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 20, 2026 3:25pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c59b5f92-c619-4cbc-ae16-b24a3899686e

📥 Commits

Reviewing files that changed from the base of the PR and between a0076bc and 242d515.

📒 Files selected for processing (1)
  • src/components/Global/Icons/Icon.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Global/Icons/Icon.tsx

Walkthrough

Replaces legacy Bridge/Manteca KYC flows with a Sumsub-based multi‑phase KYC system: adds Sumsub hooks, modals, rail status tracking, ToS actions, new KYC UI states, WebSocket handlers, types and utilities; removes obsolete Bridge/Manteca hooks, modals, identity pages, and user-details form; updates many consumers to use unified KYC predicates.

Changes

Cohort / File(s) Summary
Core Sumsub hooks & flow
src/hooks/useMultiPhaseKycFlow.ts, src/hooks/useSumsubKycFlow.ts, src/hooks/useUnifiedKycStatus.ts, src/hooks/useRailStatusTracking.ts, src/hooks/useBridgeTosStatus.ts
New multi‑phase orchestration, Sumsub flow hook, unified KYC status aggregator, rail status tracking, and Bridge ToS helper (confirm/poll).
Sumsub UI: wrapper, modals, flow components
src/components/Kyc/SumsubKycWrapper.tsx, src/components/Kyc/SumsubKycModals.tsx, src/components/Kyc/SumsubKycFlow.tsx, src/components/Kyc/KycVerificationInProgressModal.tsx, src/components/Kyc/InitiateKycModal.tsx, src/components/Kyc/BridgeTosStep.tsx, src/components/Kyc/PeanutDoesntStoreAnyPersonalInformation.tsx
New SDK wrapper, modal composition, single‑button flow, progress modal phased UI, ToS step and small UI pieces for Sumsub integration.
KYC UI states & modals
src/components/Kyc/states/*, src/components/Kyc/modals/*, src/components/Kyc/KycFailedContent.tsx, src/components/Kyc/RejectLabelsList.tsx
New modular state components: NotStarted, RequiresDocuments, ActionRequired, Failed, Completed, plus failed/action modals and reject‑label rendering.
KYC drawer / status items refactor
src/components/Kyc/KycStatusDrawer.tsx, src/components/Kyc/KycStatusItem.tsx, src/components/Kyc/KycFlow.tsx, src/components/Kyc/KYCStatusDrawerItem.tsx, src/components/Kyc/CountryRegionRow.tsx, src/components/Kyc/CountryFlagAndName.tsx
Refactors drawer/item surface to be region‑aware, keep drawer mounted for SDK, accept region/onKeepMounted props, and use unified Sumsub flow/state.
WebSocket & types
src/hooks/useWebSocket.ts, src/services/websocket.ts, src/types/sumsub-websdk.d.ts, src/interfaces/interfaces.ts
Added ws events for sumsub/user rail updates, RailStatusUpdate type, Sumsub WebSDK typings, and expanded interfaces for rails, Sumsub verification metadata and KYC modal phases.
Server actions & types
src/app/actions/sumsub.ts, src/app/actions/types/sumsub.types.ts, src/app/actions/users.ts
New initiateSumsubKyc server action and types; getBridgeTosLink/confirmBridgeTos endpoints for ToS flow.
Utilities & grouping
src/utils/kyc-grouping.utils.ts, src/constants/kyc.consts.ts, src/constants/sumsub-reject-labels.consts.ts, src/constants/bridge-requirements.consts.ts, src/utils/general.utils.ts
New grouping utility to aggregate KYC by region; unified KYC predicates; reject‑label mapping and terminal rejection logic; bridge requirement labels; useUnified checks in utils.
Flows updated to Sumsub gating
src/app/(mobile-ui)/add-money/.../bank/page.tsx, src/components/AddMoney/components/MantecaAddMoney.tsx, src/app/(mobile-ui)/withdraw/manteca/page.tsx, src/components/AddWithdraw/*
Replace prior multi‑step KYC and websocket gating with useMultiPhaseKycFlow/SumsubKycModals and InitiateKycModal; simplified step model and inline KYC initiation.
Removed legacy KYC hooks & modals / identity pages / forms
src/hooks/useBridgeKycFlow.ts, src/hooks/useMantecaKycFlow.ts, src/components/Kyc/InitiateBridgeKYCModal.tsx, src/components/Kyc/InitiateMantecaKYCModal.tsx, src/components/AddMoney/UserDetailsForm.tsx, src/components/Profile/views/*, src/components/Profile/components/IdentityVerificationCountryList.tsx
Deleted previous Bridge/Manteca hooks, modal components, identity verification pages and the user details form — functionality consolidated under Sumsub flows.
Home / History / Carousel / Completed modal changes
src/app/(mobile-ui)/history/page.tsx, src/components/Home/HomeHistory.tsx, src/components/Home/HomeCarouselCTA/index.tsx, src/components/Home/KycCompletedModal/index.tsx
Group KYC history by region, add Sumsub ws listener, introduce BridgeTosStep CTA and update KycCompletedModal to use unified status.
Profile, Claim, Request, Send, limits, wallets, UI tweaks
src/components/Profile/**, src/components/Claim/**, src/components/Request/**, src/hooks/useLimitsValidation.ts, src/features/**, src/components/Global/**, src/hooks/*
Widespread callers switched to unified predicates (isUserKycVerified / useUnifiedKycStatus), limits gating replaced with has*Limits flags, icon/status UI updates, PIX normalization tests, and minor UX adjustments across modules.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jjramirezn
  • Hugo0
  • Zishan-7
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

@coderabbitai coderabbitai bot added the enhancement New feature or request label Mar 16, 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.

Actionable comments posted: 13

Caution

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

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

75-76: ⚠️ Potential issue | 🟠 Major

Missing isUserKycApproved in useEffect dependency array.

The effect uses isUserKycApproved (via receiverKycApproved) but it's not listed in the dependency array. This could cause stale closures where the effect doesn't re-run when the user's KYC status changes.

🐛 Proposed fix
-    }, [user, senderUserId, setSenderDetails])
+    }, [user, senderUserId, setSenderDetails, isUserKycApproved])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useDetermineBankClaimType.ts` around lines 75 - 76, The useEffect
that calls determineBankClaimType references isUserKycApproved (via
receiverKycApproved) but it is not included in the dependency array, which can
cause stale closures; update the dependency array for the effect that contains
determineBankClaimType to include isUserKycApproved (or receiverKycApproved)
alongside user, senderUserId, and setSenderDetails so the effect re-runs when
KYC status changes, and remove any false-positive eslint-disable if present so
React's hook linting enforces correctness.
🧹 Nitpick comments (19)
src/components/Setup/Views/InstallPWA.tsx (1)

217-227: Consider adding loading state for consistency.

The "Continue" button in this fallback scenario is missing the loading={isSetupFlowLoading} prop that other handleNext() buttons in this component use (e.g., line 153). This could result in no visual feedback if handleNext() takes time to complete.

Suggested fix
-                <Button onClick={() => handleNext()} className="w-full" shadowSize="4" variant="purple">
+                <Button onClick={() => handleNext()} className="w-full" shadowSize="4" loading={isSetupFlowLoading}>
                     Continue
                 </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Setup/Views/InstallPWA.tsx` around lines 217 - 227, The
fallback UI in InstallPWA.tsx uses the Continue Button which calls handleNext()
but omits the loading state; update that Button (the one in the Scenario 4
return block) to pass loading={isSetupFlowLoading} so it matches other usages
(e.g., the Button at line ~153) and provides visual feedback while handleNext()
is running.
src/hooks/useCreateOnramp.ts (1)

70-73: Implement actual backend error-message extraction (or remove the misleading comment).

At Line 70, the comment says we parse backend error details, but the code currently always uses a fixed message. That drops useful server context.

Proposed fix
                 if (!response.ok) {
-                    // parse error body from backend to get specific message
-                    let errorMessage = 'Failed to create bank transfer. Please try again or contact support.'
-                    setError(errorMessage)
-                    throw new Error(errorMessage)
+                    let errorMessage = 'Failed to create bank transfer. Please try again or contact support.'
+                    try {
+                        const errorBody = await response.json()
+                        errorMessage =
+                            errorBody?.message ??
+                            errorBody?.error ??
+                            errorMessage
+                    } catch {
+                        // ignore parse errors, keep fallback message
+                    }
+                    setError(errorMessage)
+                    throw new Error(errorMessage)
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useCreateOnramp.ts` around lines 70 - 73, The comment claims we
parse backend error details but the code always sets a fixed errorMessage;
update the error handling in useCreateOnramp.ts so the catch/response handling
extracts the server-provided message (e.g., from response body or
error.response.data.message) and assign that to errorMessage before calling
setError(...) and throwing new Error(...); reference the existing errorMessage
variable, setError(...) call, and the throw new Error(...) line when
implementing the extraction logic (or remove the misleading comment if you
intentionally keep a generic message).
src/utils/__tests__/withdraw.utils.test.ts (1)

283-285: Prefer reusing production normalization logic instead of redefining it in tests.

normalizePixInput duplicates app behavior and may drift silently. If possible, export a shared normalizer and import it here so this test validates the real implementation path.

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

In `@src/utils/__tests__/withdraw.utils.test.ts` around lines 283 - 285, The test
defines a local normalizePixInput function that duplicates production logic
(using isPixEmvcoQr) and can drift; instead export the canonical normalizer from
the production module (e.g., the withdraw utils or wherever isPixEmvcoQr is
implemented) and import that into the test so the test exercises the real
normalization path; update the test to remove the local normalizePixInput and
use the exported normalizer (and keep using isPixEmvcoQr where needed) to ensure
a single source of truth.
src/services/websocket.ts (1)

5-9: Derive RailStatusUpdate.status from the shared rail model.

Typing this as plain string drops exhaustiveness checks for every consumer even though IUserRail['status'] already exists. Reusing the shared type here would keep the new WebSocket event aligned with the rest of the rail state.

♻️ Proposed type alignment
 export interface RailStatusUpdate {
     railId: string
-    status: string
+    status: IUserRail['status']
     provider?: string
 }

Import IUserRail from @/interfaces alongside the other shared types.

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

In `@src/services/websocket.ts` around lines 5 - 9, The RailStatusUpdate.status is
currently typed as a plain string which loses exhaustiveness checks; change it
to reuse the shared rail status type by importing IUserRail from '@/interfaces'
and set the status property to IUserRail['status'] in the RailStatusUpdate
interface (update the import list accordingly so the new import is included
alongside existing shared types such as any others already imported in
src/services/websocket.ts).
src/components/AddMoney/components/MantecaAddMoney.tsx (1)

73-75: Consider replaying the submit after KYC succeeds.

Line 145 aborts the deposit attempt, and Lines 223-226 only launch Sumsub. Without an onKycSuccess handler (or equivalent pending-submit flag), a newly verified user lands back on the amount step and has to press Continue again with the same values. useMultiPhaseKycFlow already exposes onKycSuccess, so this retry can stay local to the component.

Also applies to: 145-147, 220-229

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

In `@src/components/AddMoney/components/MantecaAddMoney.tsx` around lines 73 - 75,
The deposit flow aborts when KYC is required and does not automatically retry
after verification; update the component to use the existing
useMultiPhaseKycFlow() return value (sumsubFlow) by wiring its onKycSuccess
callback to resume the pending deposit: when you detect KYC is required (where
you currently set showKycModal via setShowKycModal and abort the submit), record
the pending submit parameters (or a boolean flag) in component state and open
the Sumsub modal, then implement sumsubFlow.onKycSuccess (or pass an
onKycSuccess handler) to close the modal, clear the pending flag appropriately,
and re-invoke the original submit handler (the same function that runs when
Continue is pressed) so the deposit completes automatically after successful
KYC.
src/hooks/useBridgeTosStatus.ts (1)

10-13: Prefer a ToS-specific predicate here.

This hook is deriving a ToS state, but needsBridgeTos is keyed off the generic REQUIRES_INFORMATION rail status. IUserRail.metadata.additionalRequirements already exists for the concrete unmet requirement, so tightening this check would keep the home CTA from drifting if Bridge ever reuses REQUIRES_INFORMATION for anything besides ToS.

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

In `@src/hooks/useBridgeTosStatus.ts` around lines 10 - 13, The hook
useBridgeTosStatus currently sets needsBridgeTos by testing r.status ===
'REQUIRES_INFORMATION'; change that to a ToS-specific predicate that inspects
IUserRail.metadata.additionalRequirements on bridgeRails (e.g., needsBridgeTos =
bridgeRails.some(r =>
r.metadata?.additionalRequirements?.includes('<TO_S_SPECIFIC_KEY>'))), leaving
isBridgeFullyEnabled logic as-is; replace '<TO_S_SPECIFIC_KEY>' with the
concrete additionalRequirements key used for Bridge ToS in your domain.
src/components/Kyc/states/KycRequiresDocuments.tsx (1)

24-34: Consider using the requirement string as the React key instead of label.title.

Using label.title as the key could cause duplicate key warnings if two different requirement strings produce the same title (e.g., via the fallback auto-formatting). Using the original req string would guarantee uniqueness.

Suggested fix
                     requirements.map((req) => {
                         const label = getRequirementLabel(req)
                         return (
                             <InfoCard
                                 variant="info"
-                                key={label.title}
+                                key={req}
                                 description={label.description}
                                 title={label.title}
                             />
                         )
                     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/states/KycRequiresDocuments.tsx` around lines 24 - 34, The
InfoCard list is using label.title as the React key which can collide; change
the key to use the original requirement string from requirements.map (the req
variable) instead of label.title in the map callback (requirements.map(...))
that calls getRequirementLabel and renders <InfoCard ... />; ensure req is a
stable string (or fall back to a deterministic combination like
`${req}-${index}` only if req may be non-unique) so React keys remain unique and
stable across renders.
src/components/Kyc/states/KycActionRequired.tsx (1)

23-31: Prefer Button’s built-in loading state to reduce duplicated UI logic.

Using loading={isLoading} keeps spinner/text behavior centralized in src/components/0_Bruddle/Button.tsx and avoids manual “Loading...” branching.

♻️ Suggested refactor
             <Button
                 icon={'retry' as IconName}
                 className="w-full"
                 shadowSize="4"
                 onClick={onResume}
+                loading={isLoading}
                 disabled={isLoading}
             >
-                {isLoading ? 'Loading...' : 'Re-submit verification'}
+                Re-submit verification
             </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/states/KycActionRequired.tsx` around lines 23 - 31, The
Button in KycActionRequired.tsx is manually switching its label with {isLoading
? 'Loading...' : 'Re-submit verification'} which duplicates the Button
component's built-in loading UI; update the Button usage to pass
loading={isLoading} (and keep or combine disabled={isLoading} if you still want
it disabled) and restore the static children text to "Re-submit verification" so
the Button component controls spinner/text behavior centrally; locate the Button
element using props icon, onResume, isLoading in this file to make the change.
src/components/Profile/views/RegionsVerification.view.tsx (2)

100-105: Potential stale closure in handleStartKyc dependency array.

The dependency array includes flow.handleInitiateKyc but selectedRegion is also used inside the callback. While selectedRegion is captured correctly via the state setter, this pattern can be fragile. Consider using a ref or ensuring all dependencies are listed.

♻️ Suggested fix
     const handleStartKyc = useCallback(async () => {
         const intent = selectedRegion ? getRegionIntent(selectedRegion.path) : undefined
         if (intent) setActiveRegionIntent(intent)
         setSelectedRegion(null)
         await flow.handleInitiateKyc(intent)
-    }, [flow.handleInitiateKyc, selectedRegion])
+    }, [flow, selectedRegion])

Or if flow is not stable, consider wrapping flow.handleInitiateKyc access differently.

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

In `@src/components/Profile/views/RegionsVerification.view.tsx` around lines 100 -
105, handleStartKyc captures selectedRegion but the dependency array only lists
flow.handleInitiateKyc which risks a stale closure; update the dependencies to
include selectedRegion (and any unstable flow reference) or stabilize access to
the handler. Specifically, ensure handleStartKyc depends on selectedRegion and
flow.handleInitiateKyc, or extract flow.handleInitiateKyc to a stable
ref/useCallback and read selectedRegion via a ref or by including it in the
dependency array so getRegionIntent(selectedRegion.path), setActiveRegionIntent,
setSelectedRegion, and await flow.handleInitiateKyc(intent) always use the
latest values.

56-58: Ref mutation during render is a React anti-pattern.

Line 58 mutates displayRegionRef.current during render, which can cause issues in Concurrent Mode. Move this assignment into a useEffect.

♻️ Proposed fix
     const displayRegionRef = useRef<Region | null>(null)
-    if (selectedRegion) displayRegionRef.current = selectedRegion
+    useEffect(() => {
+        if (selectedRegion) displayRegionRef.current = selectedRegion
+    }, [selectedRegion])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Profile/views/RegionsVerification.view.tsx` around lines 56 -
58, The code mutates displayRegionRef.current during render; move the assignment
into a useEffect so the ref is updated after render—create a useEffect that
depends on selectedRegion and inside set displayRegionRef.current =
selectedRegion (or null) to preserve the stable display during modal close
animation; update the logic that reads displayRegionRef so it still uses the ref
value.
src/hooks/useRailStatusTracking.ts (2)

53-61: Consider documenting the priority rationale.

The status priority (requires_documents > requires_tos > enabled > failed > setting_up) reflects that actionable states take precedence. A brief comment explaining this would help future maintainers.

📝 Suggested documentation
 // pick the "most advanced" status for a provider group
+// priority: action-required states first, then success, then failure, then pending
+// this ensures UI prompts users to complete outstanding requirements
 function deriveGroupStatus(rails: IUserRail[]): ProviderDisplayStatus {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useRailStatusTracking.ts` around lines 53 - 61, Add a short
explanatory comment above deriveGroupStatus that documents the priority
rationale for statuses (requires_documents > requires_tos > enabled > failed >
setting_up), e.g., stating that more actionable/urgent developer-facing states
(documents/tos) should override operational states so the UI surfaces required
user actions first; place this comment near the deriveGroupStatus function and
reference the statuses array/priority order used in the function to make it
clear why that ordering is chosen.

120-128: Potential race condition: polling may continue briefly after allSettled.

When allSettled becomes true, the effect clears the interval. However, if the interval callback is already executing when allSettled changes, one extra fetchUser call may occur. This is minor but worth noting.

💡 Optional: Add allSettled check inside polling callback
 pollTimerRef.current = setInterval(() => {
-    if (isMountedRef.current) {
+    if (isMountedRef.current && !allSettled) {
         fetchUser()
     }
 }, POLL_INTERVAL_MS)

Note: This would require allSettled as a dependency, which may not be desired. The current implementation is acceptable since the extra call is harmless.

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

In `@src/hooks/useRailStatusTracking.ts` around lines 120 - 128, The interval can
race with allSettled changes, so modify the polling callback (the function
scheduled in pollTimerRef via setInterval where fetchUser is invoked) to check
the current settled state before calling fetchUser; either read a fresh
allSettledRef (create and update a ref that mirrors allSettled) or directly
guard with the latest allSettled value and return early if true, ensuring the
polling callback itself prevents one extra fetch when allSettled flips to true
while the timer is executing.
src/components/IdentityVerification/StartVerificationModal.tsx (1)

64-66: Consider adding defensive handling for unknown region paths.

While DEFAULT_UNLOCK_ITEMS handles the fallback, logging unknown paths could help debug issues where regions don't match expected keys.

💡 Optional defensive logging
 const unlockItems = selectedRegion
-    ? (REGION_UNLOCK_ITEMS[selectedRegion.path] ?? DEFAULT_UNLOCK_ITEMS)
+    ? (REGION_UNLOCK_ITEMS[selectedRegion.path] ?? (() => {
+        console.warn(`Unknown region path: ${selectedRegion.path}, using defaults`)
+        return DEFAULT_UNLOCK_ITEMS
+      })())
     : DEFAULT_UNLOCK_ITEMS
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/IdentityVerification/StartVerificationModal.tsx` around lines
64 - 66, The current selection of unlockItems uses
REGION_UNLOCK_ITEMS[selectedRegion.path] with DEFAULT_UNLOCK_ITEMS as a fallback
but lacks defensive logging for unknown region paths; update the logic around
unlockItems (where selectedRegion, REGION_UNLOCK_ITEMS and DEFAULT_UNLOCK_ITEMS
are used) to detect when selectedRegion.path is present but not a key in
REGION_UNLOCK_ITEMS, and emit a debug/warn log including the unexpected path and
context before falling back to DEFAULT_UNLOCK_ITEMS so unknown keys are recorded
for troubleshooting.
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (1)

96-98: Consider adding fetchUser to the dependency array or using an explicit eslint-disable comment.

The empty dependency array [] intentionally fetches user data only on mount. While this behavior is correct for the flow, adding a comment or eslint-disable directive would clarify intent and prevent lint warnings.

💡 Suggested clarification
 useEffect(() => {
     fetchUser()
+    // eslint-disable-next-line react-hooks/exhaustive-deps -- fetch only on mount
 }, [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 96 - 98,
The useEffect currently calls fetchUser with an empty dependency array which
will trigger lint warnings; either add fetchUser to the dependency array or
explicitly silence the rule with a comment. Fix by either (a) memoizing
fetchUser (e.g., wrap fetchUser in useCallback) and then include fetchUser in
the useEffect dependency list, or (b) keep the empty array but add an
eslint-disable-next-line react-hooks/exhaustive-deps comment plus a brief
explanatory comment above the useEffect to document the intentional mount-only
behavior; reference the useEffect call that invokes fetchUser and the fetchUser
function when making the change.
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

268-268: Duplicate SumsubKycModals renders in different views.

SumsubKycModals is rendered both in the form view (line 268) and the list view (line 383). Since only one view is active at a time (controlled by view state), this shouldn't cause functional issues. However, consolidating to a single render location outside the conditional views would be cleaner.

♻️ Suggested consolidation

Move the SumsubKycModals render outside the view conditionals:

+    // Render SumsubKycModals once, outside view conditionals
+    const kycModals = <SumsubKycModals flow={sumsubFlow} />

     if (view === 'form') {
         return (
             <div className="flex min-h-[inherit] flex-col justify-normal gap-8">
                 ...
-                <SumsubKycModals flow={sumsubFlow} />
+                {kycModals}
             </div>
         )
     }

     return (
         <div className="w-full space-y-8 self-start">
             ...
-            <SumsubKycModals flow={sumsubFlow} />
+            {kycModals}
         </div>
     )

Also applies to: 383-383

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

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx` at line 268,
SumsubKycModals is rendered twice (inside the form and list conditional
branches); remove the duplicate and render a single <SumsubKycModals
flow={sumsubFlow} /> once at the parent return level outside the view
conditionals so it is present regardless of view state; update by deleting the
extra occurrence (reference: SumsubKycModals and the view state controlling
which branch renders) and keep one centralized render that passes the existing
sumsubFlow prop.
src/components/Kyc/SumsubKycWrapper.tsx (1)

11-12: Consider moving SDK URL to constants file.

The TODO comment indicates this should be moved. This would improve maintainability and align with the codebase's constants organization.

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

In `@src/components/Kyc/SumsubKycWrapper.tsx` around lines 11 - 12, The hard-coded
SUMSUB_SDK_URL constant in SumsubKycWrapper.tsx should be moved into the
project's central constants module; extract SUMSUB_SDK_URL into an existing
constants file (or create one if missing), export it, and update
SumsubKycWrapper.tsx to import and use the exported constant instead of the
inline value so the SDK URL is maintained in a single, discoverable place.
src/hooks/useMultiPhaseKycFlow.ts (1)

17-32: Consider adding error logging for ToS confirmation failures.

While the retry and polling approach is resilient (per learnings, the phase-transition effect validates via rail status), silently swallowing errors from confirmBridgeTos() may make debugging difficult. Consider logging when the first attempt fails before retrying.

♻️ Optional: Add debug logging for ToS confirmation
 export async function confirmBridgeTosAndAwaitRails(fetchUser: () => Promise<any>) {
     const result = await confirmBridgeTos()
     if (!result.data?.accepted) {
+        console.debug('[confirmBridgeTosAndAwaitRails] first attempt not accepted, retrying...')
         await new Promise((resolve) => setTimeout(resolve, 2000))
         await confirmBridgeTos()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useMultiPhaseKycFlow.ts` around lines 17 - 32, The function
confirmBridgeTosAndAwaitRails silently swallows failures from
confirmBridgeTos(), making debugging hard; update confirmBridgeTosAndAwaitRails
to catch and log errors on the initial confirmBridgeTos() call (and optionally
on the second retry) using your app logger (or console) so failures are visible,
e.g., wrap the first await confirmBridgeTos() in try/catch, log the error with
context including the function name and attempt, then proceed with the existing
retry/polling logic; reference confirmBridgeTosAndAwaitRails, confirmBridgeTos,
and fetchUser to locate where to add the try/catch and logging.
src/components/Kyc/KycVerificationInProgressModal.tsx (2)

12-12: Unused prop onSkipTerms.

The onSkipTerms prop is defined in the interface but never used within the component. If this is intentional for future use, consider adding a TODO comment. Otherwise, remove it to reduce interface surface area.

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

In `@src/components/Kyc/KycVerificationInProgressModal.tsx` at line 12, The prop
onSkipTerms in KycVerificationInProgressModal is declared but never used; either
remove it from the component's props/interface to shrink the surface area or, if
it's intentionally reserved, add a clear TODO comment next to the onSkipTerms
declaration explaining planned usage. Locate the props/interface where
onSkipTerms is defined and either delete that property and its type, or annotate
it with a TODO and keep it unused until the future implementation in the
KycVerificationInProgressModal component.

44-44: Unnecessary type assertion.

The string literals 'clock' and 'check' (also on lines 109, 134) are valid members of the IconName union type. The as IconName casts are redundant and can be removed for cleaner code.

♻️ Proposed simplification
-                icon={'clock' as IconName}
+                icon="clock"

Apply similarly to lines 109 and 134 for 'check'.

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

In `@src/components/Kyc/KycVerificationInProgressModal.tsx` at line 44, Remove the
redundant type assertions on literal icon props in the
KycVerificationInProgressModal component: replace occurrences like icon={'clock'
as IconName} and the similar 'check' usages with plain string literals
(icon='clock', icon='check'). Update all three instances referenced in the
component (the icon prop near the top and the two 'check' usages around the
success/complete UI) so the literal values rely on TypeScript's literal type
inference instead of the unnecessary "as IconName" casts.
🤖 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/Claim/Link/MantecaFlowManager.tsx`:
- Around line 121-125: The onVerify handler currently calls
setShowKycModal(false) unconditionally after awaiting
sumsubFlow.handleInitiateKyc('LATAM'), which hides the starter modal even if
initiation fails; change the handler so it only closes the modal when initiation
actually succeeds—either by having handleInitiateKyc return a success boolean
(e.g., true on success, false/throw on failure) and calling
setShowKycModal(false) only when that value is true, or by catching errors from
sumsubFlow.handleInitiateKyc and only calling setShowKycModal(false) in the
success path (leave the modal open on error to allow retry). Ensure you update
(or use) the sumsubFlow.handleInitiateKyc signature and the onVerify callback
accordingly.
- Around line 30-31: The component currently gates the Sumsub flow and modal
visibility on isUserMantecaKycApproved from useKycStatus(), which misses the
unified approval flag and doesn’t synchronize modal state when auth resolves;
update the logic to use isUserKycApproved (from useKycStatus()) as the primary
gate for launching/hiding the modal and ensure the modal open state mirrors the
unified approval both ways (i.e., if isUserKycApproved becomes true, close the
modal; if modal actions set approval, update any local/open state accordingly),
and keep isUserMantecaKycApproved in sync with isUserKycApproved where needed so
both flags reflect the same approved state.

In `@src/components/Kyc/BridgeTosStep.tsx`:
- Around line 61-67: The handler handleIframeClose currently hides the iframe
immediately via setShowIframe(false) then awaits
confirmBridgeTosAndAwaitRails(fetchUser), which can throw and leave the UI
blank; modify handleIframeClose to wrap the await
confirmBridgeTosAndAwaitRails(fetchUser) call in a try/catch: only remove/hide
the iframe (or call onComplete) after the confirm call succeeds, and on error
route the failure into the existing error modal flow or call onSkip so the user
has retry/skip options (use the confirmBridgeTosAndAwaitRails, setShowIframe,
onComplete, onSkip symbols to locate and update the logic).

In `@src/components/Kyc/modals/KycProcessingModal.tsx`:
- Line 17: The hard-coded description string in KycProcessingModal ("We're
reviewing your identity. This usually takes less than a minute.") promises a
sub-minute review; replace it with neutral copy (e.g., "We're reviewing your
identity. This may take a few minutes." or "This may take some time.") or, if
you have a status prop (e.g., kycStatus / isUnderReview), conditionally render a
different message for manual reviews vs. automated pending states; update the
description prop in KycProcessingModal accordingly so the modal no longer
guarantees "less than a minute."

In `@src/components/Kyc/SumsubKycFlow.tsx`:
- Around line 23-25: The Button click currently calls flow.handleInitiateKyc()
without passing the regionIntent prop even though useMultiPhaseKycFlow was
initialized with regionIntent; update the call site in SumsubKycFlow so the
click handler explicitly passes the regionIntent (i.e., change
flow.handleInitiateKyc() to flow.handleInitiateKyc(regionIntent)), keeping the
existing disabled and children logic intact; this keeps usage consistent with
other components using useMultiPhaseKycFlow and makes the intent explicit when
invoking handleInitiateKyc.

In `@src/components/Kyc/SumsubKycModals.tsx`:
- Around line 4-8: The prop type SumsubKycModalsProps uses ReturnType<typeof
useMultiPhaseKycFlow>, but useMultiPhaseKycFlow was imported as a type-only
import which removes the value binding required by typeof; change the import of
useMultiPhaseKycFlow to a regular (value) import so typeof can reference the
runtime binding (i.e., import useMultiPhaseKycFlow without the "type" modifier)
and keep the ReturnType<typeof useMultiPhaseKycFlow> in the SumsubKycModalsProps
definition.

In `@src/components/Profile/components/ProfileMenuItem.tsx`:
- Around line 51-57: Replace the semantically incorrect <label> element inside
ProfileMenuItem with a non-form inline element (e.g., <span>) for the {label}
text, and make the highlight indicator accessible by adding an appropriate
accessible name that reflects the real state (for example, add aria-label="KYC
verification pending" or aria-describedby pointing to hidden text when highlight
is true, driven by the existing highlight prop / isUserKycApproved logic);
update the element that renders the pulsing dot (the block using className
'animate-pulse' and aria-label currently "highlight-indicator") to expose that
state to assistive tech rather than hiding it (remove aria-hidden and provide
the descriptive aria-label or connect to visible/visually-hidden text).

In `@src/constants/sumsub-reject-labels.consts.ts`:
- Around line 175-178: The description for the FRAUDULENT_LIVENESS entry is
inconsistent with its use as a terminal rejection; update the REJECT_LABEL_MAP
entry for FRAUDULENT_LIVENESS so the message reflects permanent rejection (e.g.,
explain the decision is final and provide next steps or contact support) instead
of suggesting a retry; locate and change the FRAUDULENT_LIVENESS object in
REJECT_LABEL_MAP and also scan similar entries mentioned (the other inconsistent
entries in the same file) to ensure all TERMINAL_REJECT_LABELS have matching
terminal-style descriptions.

In `@src/content`:
- Line 1: The submodule at path 'src/content' points to an inaccessible remote
URL 'https://github.com/peanutprotocol/peanut-content.git'; update the
.gitmodules entry for src/content to the correct reachable repository URL (or
remove the submodule if it's no longer needed), then run git submodule sync and
git submodule update --init --recursive to apply the change; if removing, also
remove the src/content entry from .gitmodules and .git/config and commit the
removal (or use git rm --cached src/content) so CI and fresh clones no longer
attempt to fetch the broken remote.

In `@src/features/limits/hooks/useLimitsValidation.ts`:
- Around line 48-55: The hook currently treats missing limits (e.g.,
mantecaLimits/bridgeLimits falsey) as "no limits" via
hasMantecaLimits/hasBridgeLimits and returns a permissive result; instead detect
and surface an unknown-limits state when the underlying useLimits hook reports
an error or when limits are not loaded (isLoading) so we don't fail open. Update
useLimitsValidation (references: mantecaLimits, bridgeLimits, isLoading,
hasMantecaLimits, hasBridgeLimits, isLocalUser, mapToLimitCurrency) to: 1) read
the error/fetch status from useLimits (use the error or fetch flag exported by
useLimits), 2) if error is present or the limits arrays are undefined/null while
not explicitly empty, return/emit an "unknownLimits" result (or set a boolean
like hasUnknownLimits) instead of treating hasMantecaLimits/hasBridgeLimits as
false, and 3) propagate that unknown state to callers so client-side limit
checks can fail-closed; apply the same change to the similar logic around lines
256-275.

In `@src/features/limits/views/LimitsPageView.tsx`:
- Around line 23-24: The routing decision uses hasMantecaLimits from useLimits
before limits finish loading, causing incorrect provider routes on early clicks;
update useLimits to return an isLoading flag (or consume existing one) and in
LimitsPageView guard click handlers and any routing logic (where
hasMantecaLimits is read) by checking !isLoading first — disable or no-op the
click path and/or show a loading state until isLoading is false, then compute
the provider route using the settled hasMantecaLimits value (update handlers in
LimitsPageView that reference hasMantecaLimits, including the click paths around
lines 68-69).

In `@src/hooks/useUnifiedKycStatus.ts`:
- Around line 64-87: The hook useUnifiedKycStatus computes isSumsubInProgress
(via useMemo) but never returns it; update the return object of
useUnifiedKycStatus to include isSumsubInProgress (e.g., alongside the other
Sumsub fields like isSumsubApproved and sumsubStatus) so consumers can read
Sumsub progress state; ensure you export it in the same shape as the other flags
so existing callers can destructure it (no other logic changes needed since
isKycInProgress already derives from isSumsubInProgress).

In `@src/utils/kyc-grouping.utils.ts`:
- Around line 55-57: The sort comparator for latamVerifications can pass
undefined updatedAt (per IUserKycVerification) into new Date(), producing
Invalid Date and breaking ordering; update the comparator used to compute
latamVerification to guard against undefined by normalizing updatedAt for both a
and b (e.g., use a.updatedAt && !isNaN(Date.parse(a.updatedAt)) ?
Date.parse(a.updatedAt) : 0) so the comparator always compares numeric
timestamps (or filter out entries with no valid updatedAt before sorting), and
ensure you reference the latamVerifications array and latamVerification
assignment when applying the fix.

---

Outside diff comments:
In `@src/hooks/useDetermineBankClaimType.ts`:
- Around line 75-76: The useEffect that calls determineBankClaimType references
isUserKycApproved (via receiverKycApproved) but it is not included in the
dependency array, which can cause stale closures; update the dependency array
for the effect that contains determineBankClaimType to include isUserKycApproved
(or receiverKycApproved) alongside user, senderUserId, and setSenderDetails so
the effect re-runs when KYC status changes, and remove any false-positive
eslint-disable if present so React's hook linting enforces correctness.

---

Nitpick comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 96-98: The useEffect currently calls fetchUser with an empty
dependency array which will trigger lint warnings; either add fetchUser to the
dependency array or explicitly silence the rule with a comment. Fix by either
(a) memoizing fetchUser (e.g., wrap fetchUser in useCallback) and then include
fetchUser in the useEffect dependency list, or (b) keep the empty array but add
an eslint-disable-next-line react-hooks/exhaustive-deps comment plus a brief
explanatory comment above the useEffect to document the intentional mount-only
behavior; reference the useEffect call that invokes fetchUser and the fetchUser
function when making the change.

In `@src/components/AddMoney/components/MantecaAddMoney.tsx`:
- Around line 73-75: The deposit flow aborts when KYC is required and does not
automatically retry after verification; update the component to use the existing
useMultiPhaseKycFlow() return value (sumsubFlow) by wiring its onKycSuccess
callback to resume the pending deposit: when you detect KYC is required (where
you currently set showKycModal via setShowKycModal and abort the submit), record
the pending submit parameters (or a boolean flag) in component state and open
the Sumsub modal, then implement sumsubFlow.onKycSuccess (or pass an
onKycSuccess handler) to close the modal, clear the pending flag appropriately,
and re-invoke the original submit handler (the same function that runs when
Continue is pressed) so the deposit completes automatically after successful
KYC.

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Line 268: SumsubKycModals is rendered twice (inside the form and list
conditional branches); remove the duplicate and render a single <SumsubKycModals
flow={sumsubFlow} /> once at the parent return level outside the view
conditionals so it is present regardless of view state; update by deleting the
extra occurrence (reference: SumsubKycModals and the view state controlling
which branch renders) and keep one centralized render that passes the existing
sumsubFlow prop.

In `@src/components/IdentityVerification/StartVerificationModal.tsx`:
- Around line 64-66: The current selection of unlockItems uses
REGION_UNLOCK_ITEMS[selectedRegion.path] with DEFAULT_UNLOCK_ITEMS as a fallback
but lacks defensive logging for unknown region paths; update the logic around
unlockItems (where selectedRegion, REGION_UNLOCK_ITEMS and DEFAULT_UNLOCK_ITEMS
are used) to detect when selectedRegion.path is present but not a key in
REGION_UNLOCK_ITEMS, and emit a debug/warn log including the unexpected path and
context before falling back to DEFAULT_UNLOCK_ITEMS so unknown keys are recorded
for troubleshooting.

In `@src/components/Kyc/KycVerificationInProgressModal.tsx`:
- Line 12: The prop onSkipTerms in KycVerificationInProgressModal is declared
but never used; either remove it from the component's props/interface to shrink
the surface area or, if it's intentionally reserved, add a clear TODO comment
next to the onSkipTerms declaration explaining planned usage. Locate the
props/interface where onSkipTerms is defined and either delete that property and
its type, or annotate it with a TODO and keep it unused until the future
implementation in the KycVerificationInProgressModal component.
- Line 44: Remove the redundant type assertions on literal icon props in the
KycVerificationInProgressModal component: replace occurrences like icon={'clock'
as IconName} and the similar 'check' usages with plain string literals
(icon='clock', icon='check'). Update all three instances referenced in the
component (the icon prop near the top and the two 'check' usages around the
success/complete UI) so the literal values rely on TypeScript's literal type
inference instead of the unnecessary "as IconName" casts.

In `@src/components/Kyc/states/KycActionRequired.tsx`:
- Around line 23-31: The Button in KycActionRequired.tsx is manually switching
its label with {isLoading ? 'Loading...' : 'Re-submit verification'} which
duplicates the Button component's built-in loading UI; update the Button usage
to pass loading={isLoading} (and keep or combine disabled={isLoading} if you
still want it disabled) and restore the static children text to "Re-submit
verification" so the Button component controls spinner/text behavior centrally;
locate the Button element using props icon, onResume, isLoading in this file to
make the change.

In `@src/components/Kyc/states/KycRequiresDocuments.tsx`:
- Around line 24-34: The InfoCard list is using label.title as the React key
which can collide; change the key to use the original requirement string from
requirements.map (the req variable) instead of label.title in the map callback
(requirements.map(...)) that calls getRequirementLabel and renders <InfoCard ...
/>; ensure req is a stable string (or fall back to a deterministic combination
like `${req}-${index}` only if req may be non-unique) so React keys remain
unique and stable across renders.

In `@src/components/Kyc/SumsubKycWrapper.tsx`:
- Around line 11-12: The hard-coded SUMSUB_SDK_URL constant in
SumsubKycWrapper.tsx should be moved into the project's central constants
module; extract SUMSUB_SDK_URL into an existing constants file (or create one if
missing), export it, and update SumsubKycWrapper.tsx to import and use the
exported constant instead of the inline value so the SDK URL is maintained in a
single, discoverable place.

In `@src/components/Profile/views/RegionsVerification.view.tsx`:
- Around line 100-105: handleStartKyc captures selectedRegion but the dependency
array only lists flow.handleInitiateKyc which risks a stale closure; update the
dependencies to include selectedRegion (and any unstable flow reference) or
stabilize access to the handler. Specifically, ensure handleStartKyc depends on
selectedRegion and flow.handleInitiateKyc, or extract flow.handleInitiateKyc to
a stable ref/useCallback and read selectedRegion via a ref or by including it in
the dependency array so getRegionIntent(selectedRegion.path),
setActiveRegionIntent, setSelectedRegion, and await
flow.handleInitiateKyc(intent) always use the latest values.
- Around line 56-58: The code mutates displayRegionRef.current during render;
move the assignment into a useEffect so the ref is updated after render—create a
useEffect that depends on selectedRegion and inside set displayRegionRef.current
= selectedRegion (or null) to preserve the stable display during modal close
animation; update the logic that reads displayRegionRef so it still uses the ref
value.

In `@src/components/Setup/Views/InstallPWA.tsx`:
- Around line 217-227: The fallback UI in InstallPWA.tsx uses the Continue
Button which calls handleNext() but omits the loading state; update that Button
(the one in the Scenario 4 return block) to pass loading={isSetupFlowLoading} so
it matches other usages (e.g., the Button at line ~153) and provides visual
feedback while handleNext() is running.

In `@src/hooks/useBridgeTosStatus.ts`:
- Around line 10-13: The hook useBridgeTosStatus currently sets needsBridgeTos
by testing r.status === 'REQUIRES_INFORMATION'; change that to a ToS-specific
predicate that inspects IUserRail.metadata.additionalRequirements on bridgeRails
(e.g., needsBridgeTos = bridgeRails.some(r =>
r.metadata?.additionalRequirements?.includes('<TO_S_SPECIFIC_KEY>'))), leaving
isBridgeFullyEnabled logic as-is; replace '<TO_S_SPECIFIC_KEY>' with the
concrete additionalRequirements key used for Bridge ToS in your domain.

In `@src/hooks/useCreateOnramp.ts`:
- Around line 70-73: The comment claims we parse backend error details but the
code always sets a fixed errorMessage; update the error handling in
useCreateOnramp.ts so the catch/response handling extracts the server-provided
message (e.g., from response body or error.response.data.message) and assign
that to errorMessage before calling setError(...) and throwing new Error(...);
reference the existing errorMessage variable, setError(...) call, and the throw
new Error(...) line when implementing the extraction logic (or remove the
misleading comment if you intentionally keep a generic message).

In `@src/hooks/useMultiPhaseKycFlow.ts`:
- Around line 17-32: The function confirmBridgeTosAndAwaitRails silently
swallows failures from confirmBridgeTos(), making debugging hard; update
confirmBridgeTosAndAwaitRails to catch and log errors on the initial
confirmBridgeTos() call (and optionally on the second retry) using your app
logger (or console) so failures are visible, e.g., wrap the first await
confirmBridgeTos() in try/catch, log the error with context including the
function name and attempt, then proceed with the existing retry/polling logic;
reference confirmBridgeTosAndAwaitRails, confirmBridgeTos, and fetchUser to
locate where to add the try/catch and logging.

In `@src/hooks/useRailStatusTracking.ts`:
- Around line 53-61: Add a short explanatory comment above deriveGroupStatus
that documents the priority rationale for statuses (requires_documents >
requires_tos > enabled > failed > setting_up), e.g., stating that more
actionable/urgent developer-facing states (documents/tos) should override
operational states so the UI surfaces required user actions first; place this
comment near the deriveGroupStatus function and reference the statuses
array/priority order used in the function to make it clear why that ordering is
chosen.
- Around line 120-128: The interval can race with allSettled changes, so modify
the polling callback (the function scheduled in pollTimerRef via setInterval
where fetchUser is invoked) to check the current settled state before calling
fetchUser; either read a fresh allSettledRef (create and update a ref that
mirrors allSettled) or directly guard with the latest allSettled value and
return early if true, ensuring the polling callback itself prevents one extra
fetch when allSettled flips to true while the timer is executing.

In `@src/services/websocket.ts`:
- Around line 5-9: The RailStatusUpdate.status is currently typed as a plain
string which loses exhaustiveness checks; change it to reuse the shared rail
status type by importing IUserRail from '@/interfaces' and set the status
property to IUserRail['status'] in the RailStatusUpdate interface (update the
import list accordingly so the new import is included alongside existing shared
types such as any others already imported in src/services/websocket.ts).

In `@src/utils/__tests__/withdraw.utils.test.ts`:
- Around line 283-285: The test defines a local normalizePixInput function that
duplicates production logic (using isPixEmvcoQr) and can drift; instead export
the canonical normalizer from the production module (e.g., the withdraw utils or
wherever isPixEmvcoQr is implemented) and import that into the test so the test
exercises the real normalization path; update the test to remove the local
normalizePixInput and use the exported normalizer (and keep using isPixEmvcoQr
where needed) to ensure a single source of truth.
🪄 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: c1898c41-ffe3-4dc5-ab95-b3cbb54bc754

📥 Commits

Reviewing files that changed from the base of the PR and between 037a834 and a0076bc.

📒 Files selected for processing (100)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/history/page.tsx
  • src/app/(mobile-ui)/home/page.tsx
  • src/app/(mobile-ui)/points/invites/page.tsx
  • src/app/(mobile-ui)/points/page.tsx
  • src/app/(mobile-ui)/profile/identity-verification/[region]/[country]/page.tsx
  • src/app/(mobile-ui)/profile/identity-verification/[region]/page.tsx
  • src/app/(mobile-ui)/profile/identity-verification/layout.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/actions/sumsub.ts
  • src/app/actions/types/sumsub.types.ts
  • src/app/actions/users.ts
  • src/components/AddMoney/UserDetailsForm.tsx
  • 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/Claim/Link/views/BankFlowManager.view.tsx
  • src/components/Global/Badges/StatusBadge.tsx
  • src/components/Global/Icons/Icon.tsx
  • src/components/Global/IframeWrapper/StartVerificationView.tsx
  • src/components/Global/IframeWrapper/index.tsx
  • src/components/Global/PostSignupActionManager/index.tsx
  • src/components/Global/StatusPill/index.tsx
  • src/components/Home/HomeCarouselCTA/index.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Home/KycCompletedModal/index.tsx
  • src/components/IdentityVerification/StartVerificationModal.tsx
  • src/components/Kyc/BridgeTosStep.tsx
  • src/components/Kyc/CountryFlagAndName.tsx
  • src/components/Kyc/CountryRegionRow.tsx
  • src/components/Kyc/InitiateBridgeKYCModal.tsx
  • src/components/Kyc/InitiateKycModal.tsx
  • src/components/Kyc/InitiateMantecaKYCModal.tsx
  • src/components/Kyc/KYCStatusDrawerItem.tsx
  • src/components/Kyc/KycFailedContent.tsx
  • src/components/Kyc/KycFlow.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/KycVerificationInProgressModal.tsx
  • src/components/Kyc/PeanutDoesntStoreAnyPersonalInformation.tsx
  • src/components/Kyc/RejectLabelsList.tsx
  • src/components/Kyc/SumsubKycFlow.tsx
  • src/components/Kyc/SumsubKycModals.tsx
  • src/components/Kyc/SumsubKycWrapper.tsx
  • src/components/Kyc/modals/KycActionRequiredModal.tsx
  • src/components/Kyc/modals/KycFailedModal.tsx
  • src/components/Kyc/modals/KycProcessingModal.tsx
  • src/components/Kyc/states/KycActionRequired.tsx
  • src/components/Kyc/states/KycCompleted.tsx
  • src/components/Kyc/states/KycFailed.tsx
  • src/components/Kyc/states/KycNotStarted.tsx
  • src/components/Kyc/states/KycRequiresDocuments.tsx
  • src/components/Profile/components/IdentityVerificationCountryList.tsx
  • src/components/Profile/components/ProfileMenuItem.tsx
  • src/components/Profile/components/PublicProfile.tsx
  • src/components/Profile/index.tsx
  • src/components/Profile/views/IdentityVerification.view.tsx
  • src/components/Profile/views/ProfileEdit.view.tsx
  • src/components/Profile/views/RegionsPage.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/components/Setup/Views/InstallPWA.tsx
  • src/components/TransactionDetails/TransactionCard.tsx
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx
  • src/constants/bridge-requirements.consts.ts
  • src/constants/kyc.consts.ts
  • src/constants/sumsub-reject-labels.consts.ts
  • src/content
  • src/context/authContext.tsx
  • src/features/limits/hooks/useLimitsValidation.ts
  • src/features/limits/views/BridgeLimitsView.tsx
  • src/features/limits/views/LimitsPageView.tsx
  • src/features/payments/flows/direct-send/useDirectSendFlow.ts
  • src/features/payments/flows/semantic-request/useSemanticRequestFlow.ts
  • src/features/payments/shared/components/SendWithPeanutCta.tsx
  • src/hooks/useBridgeKycFlow.ts
  • src/hooks/useBridgeTosStatus.ts
  • src/hooks/useCreateOnramp.ts
  • src/hooks/useDetermineBankClaimType.ts
  • src/hooks/useDetermineBankRequestType.ts
  • src/hooks/useHomeCarouselCTAs.tsx
  • src/hooks/useIdentityVerification.tsx
  • src/hooks/useKycStatus.tsx
  • src/hooks/useMantecaKycFlow.ts
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/hooks/useQrKycGate.ts
  • src/hooks/useRailStatusTracking.ts
  • src/hooks/useSumsubKycFlow.ts
  • src/hooks/useUnifiedKycStatus.ts
  • src/hooks/useWebSocket.ts
  • src/interfaces/interfaces.ts
  • src/services/websocket.ts
  • src/types/sumsub-websdk.d.ts
  • src/utils/__tests__/withdraw.utils.test.ts
  • src/utils/general.utils.ts
  • src/utils/kyc-grouping.utils.ts
💤 Files with no reviewable changes (11)
  • src/app/(mobile-ui)/profile/identity-verification/[region]/[country]/page.tsx
  • src/components/Profile/components/IdentityVerificationCountryList.tsx
  • src/hooks/useMantecaKycFlow.ts
  • src/components/AddWithdraw/DynamicBankAccountForm.tsx
  • src/app/(mobile-ui)/profile/identity-verification/[region]/page.tsx
  • src/components/Profile/views/IdentityVerification.view.tsx
  • src/components/AddMoney/UserDetailsForm.tsx
  • src/hooks/useBridgeKycFlow.ts
  • src/components/Kyc/InitiateMantecaKYCModal.tsx
  • src/components/Kyc/InitiateBridgeKYCModal.tsx
  • src/components/Profile/views/RegionsPage.view.tsx

Comment on lines +30 to 31
const { isUserMantecaKycApproved } = useKycStatus()

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Gate this on unified approval and mirror the state both ways.

The flow now launches Sumsub LATAM verification, but the modal still keys off isUserMantecaKycApproved. A user who is already approved through that new path will still see this prompt, and the current one-way effect also leaves the modal open if an initial falsey auth state later resolves to approved. The shared useKycStatus() hook already exposes isUserKycApproved.

Suggested fix
-    const { isUserMantecaKycApproved } = useKycStatus()
+    const { isUserKycApproved } = useKycStatus()
@@
-    useEffect(() => {
-        if (!isUserMantecaKycApproved) {
-            setShowKycModal(true)
-        }
-    }, [isUserMantecaKycApproved])
+    useEffect(() => {
+        setShowKycModal(!isUserKycApproved)
+    }, [isUserKycApproved])

Also applies to: 43-48

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

In `@src/components/Claim/Link/MantecaFlowManager.tsx` around lines 30 - 31, The
component currently gates the Sumsub flow and modal visibility on
isUserMantecaKycApproved from useKycStatus(), which misses the unified approval
flag and doesn’t synchronize modal state when auth resolves; update the logic to
use isUserKycApproved (from useKycStatus()) as the primary gate for
launching/hiding the modal and ensure the modal open state mirrors the unified
approval both ways (i.e., if isUserKycApproved becomes true, close the modal; if
modal actions set approval, update any local/open state accordingly), and keep
isUserMantecaKycApproved in sync with isUserKycApproved where needed so both
flags reflect the same approved state.

Comment on lines +121 to +125
onClose={() => setShowKycModal(false)}
onVerify={async () => {
await sumsubFlow.handleInitiateKyc('LATAM')
setShowKycModal(false)
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't dismiss the starter modal when initiation fails.

setShowKycModal(false) runs after await sumsubFlow.handleInitiateKyc('LATAM') regardless of whether the flow actually advanced. On an API or network failure, the user loses the retry entry point and is left in the claim flow with no active KYC surface. Close it only after a successful transition, or have handleInitiateKyc return a success flag.

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

In `@src/components/Claim/Link/MantecaFlowManager.tsx` around lines 121 - 125, The
onVerify handler currently calls setShowKycModal(false) unconditionally after
awaiting sumsubFlow.handleInitiateKyc('LATAM'), which hides the starter modal
even if initiation fails; change the handler so it only closes the modal when
initiation actually succeeds—either by having handleInitiateKyc return a success
boolean (e.g., true on success, false/throw on failure) and calling
setShowKycModal(false) only when that value is true, or by catching errors from
sumsubFlow.handleInitiateKyc and only calling setShowKycModal(false) in the
success path (leave the modal open on error to allow retry). Ensure you update
(or use) the sumsubFlow.handleInitiateKyc signature and the onVerify callback
accordingly.

Comment on lines +61 to +67
const handleIframeClose = useCallback(
async (source?: 'manual' | 'completed' | 'tos_accepted') => {
setShowIframe(false)

if (source === 'tos_accepted') {
await confirmBridgeTosAndAwaitRails(fetchUser)
onComplete()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle post-iframe failures before leaving the user on a blank state.

The iframe is hidden before confirmBridgeTosAndAwaitRails(fetchUser) runs. If that call throws, this component ends up with showIframe = false and error = null, so the user gets no retry or skip path. Wrap the await in try/catch and route failures through the existing error modal or onSkip.

🩹 Proposed fix
             if (source === 'tos_accepted') {
-                await confirmBridgeTosAndAwaitRails(fetchUser)
-                onComplete()
+                try {
+                    await confirmBridgeTosAndAwaitRails(fetchUser)
+                    onComplete()
+                } catch {
+                    setError('Something went wrong. You can accept terms later from your activity feed.')
+                }
             } else {
                 onSkip()
             }

Based on learnings, the Bridge ToS flow is intended to stay resilient to transient confirmation failures instead of leaving the user stuck.

📝 Committable suggestion

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

Suggested change
const handleIframeClose = useCallback(
async (source?: 'manual' | 'completed' | 'tos_accepted') => {
setShowIframe(false)
if (source === 'tos_accepted') {
await confirmBridgeTosAndAwaitRails(fetchUser)
onComplete()
const handleIframeClose = useCallback(
async (source?: 'manual' | 'completed' | 'tos_accepted') => {
setShowIframe(false)
if (source === 'tos_accepted') {
try {
await confirmBridgeTosAndAwaitRails(fetchUser)
onComplete()
} catch {
setError('Something went wrong. You can accept terms later from your activity feed.')
}
} else {
onSkip()
}
🤖 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 - 67, The handler
handleIframeClose currently hides the iframe immediately via
setShowIframe(false) then awaits confirmBridgeTosAndAwaitRails(fetchUser), which
can throw and leave the UI blank; modify handleIframeClose to wrap the await
confirmBridgeTosAndAwaitRails(fetchUser) call in a try/catch: only remove/hide
the iframe (or call onComplete) after the confirm call succeeds, and on error
route the failure into the existing error modal flow or call onSkip so the user
has retry/skip options (use the confirmBridgeTosAndAwaitRails, setShowIframe,
onComplete, onSkip symbols to locate and update the logic).

icon="clock"
iconContainerClassName="bg-yellow-1"
title="Verification in progress"
description="We're reviewing your identity. This usually takes less than a minute."
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid promising sub-minute reviews in the in-review state.

This modal is shown for pending and under-review users, but Line 17 hard-codes “less than a minute.” That will read like a broken promise for manual reviews, which can legitimately take much longer.

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

In `@src/components/Kyc/modals/KycProcessingModal.tsx` at line 17, The hard-coded
description string in KycProcessingModal ("We're reviewing your identity. This
usually takes less than a minute.") promises a sub-minute review; replace it
with neutral copy (e.g., "We're reviewing your identity. This may take a few
minutes." or "This may take some time.") or, if you have a status prop (e.g.,
kycStatus / isUnderReview), conditionally render a different message for manual
reviews vs. automated pending states; update the description prop in
KycProcessingModal accordingly so the modal no longer guarantees "less than a
minute."

Comment on lines +23 to +25
<Button onClick={() => flow.handleInitiateKyc()} disabled={flow.isLoading} {...buttonProps}>
{flow.isLoading ? 'Loading...' : (buttonProps.children ?? 'Start Verification')}
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how handleInitiateKyc uses regionIntent
ast-grep --pattern $'handleInitiateKyc = useCallback($$$) => {
  $$$
}'

Repository: peanutprotocol/peanut-ui

Length of output: 50


🏁 Script executed:

# Find useMultiPhaseKycFlow hook
fd -t f "useMultiPhaseKycFlow" --type f

Repository: peanutprotocol/peanut-ui

Length of output: 101


🏁 Script executed:

# Search for handleInitiateKyc definition and usage
rg "handleInitiateKyc" -A 5 -B 2

Repository: peanutprotocol/peanut-ui

Length of output: 14492


🏁 Script executed:

# Check the SumsubKycFlow.tsx file context
fd -t f "SumsubKycFlow.tsx" --type f

Repository: peanutprotocol/peanut-ui

Length of output: 104


🏁 Script executed:

# Read SumsubKycFlow.tsx to see the component signature and hook initialization
cat -n src/components/Kyc/SumsubKycFlow.tsx

Repository: peanutprotocol/peanut-ui

Length of output: 1558


Pass regionIntent explicitly to handleInitiateKyc() for consistency.

The regionIntent prop is passed to useMultiPhaseKycFlow (line 19), but flow.handleInitiateKyc() is called without arguments (line 23). While handleInitiateKyc will fall back to the hook's regionIntent if no override is provided, this pattern is inconsistent with how other components use this hook. Across the codebase (AddWithdrawCountriesList, BankFlowManager, MantecaFlowManager), the pattern is to initialize useMultiPhaseKycFlow without regionIntent and explicitly pass the intent at call time: handleInitiateKyc('STANDARD'). Either make regionIntent a required prop or pass it explicitly: flow.handleInitiateKyc(regionIntent).

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

In `@src/components/Kyc/SumsubKycFlow.tsx` around lines 23 - 25, The Button click
currently calls flow.handleInitiateKyc() without passing the regionIntent prop
even though useMultiPhaseKycFlow was initialized with regionIntent; update the
call site in SumsubKycFlow so the click handler explicitly passes the
regionIntent (i.e., change flow.handleInitiateKyc() to
flow.handleInitiateKyc(regionIntent)), keeping the existing disabled and
children logic intact; this keeps usage consistent with other components using
useMultiPhaseKycFlow and makes the intent explicit when invoking
handleInitiateKyc.

@@ -1 +1 @@
Subproject commit 1741a4f2cb356e39e081f3bdcaac557f154926a8
Subproject commit ffc4bdd8ac3925b9f80d77bb5bb6e5f85a3d45b4
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Extract submodule URL for src/content from .gitmodules
url=$(git config -f .gitmodules --get submodule.src/content.url)
sha=$(git ls-tree HEAD src/content | awk '{print $3}')

echo "Submodule URL: $url"
echo "Pinned SHA:    $sha"

# Verify the SHA is advertised by the remote
git ls-remote "$url" | awk '{print $1}' | rg -n "^${sha}$" >/dev/null \
  && echo "OK: SHA is reachable on remote" \
  || { echo "ERROR: SHA not found on remote"; exit 1; }

Repository: peanutprotocol/peanut-ui

Length of output: 353


Critical: Submodule remote is inaccessible, preventing clones and CI.

The repository configured for src/content at https://github.com/peanutprotocol/peanut-content.git is not accessible (404 from GitHub). Fresh clones and CI will fail when attempting to checkout this submodule. Either the remote URL is incorrect, the repository has been deleted, or access is restricted. Fix the submodule configuration before merging.

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

In `@src/content` at line 1, The submodule at path 'src/content' points to an
inaccessible remote URL 'https://github.com/peanutprotocol/peanut-content.git';
update the .gitmodules entry for src/content to the correct reachable repository
URL (or remove the submodule if it's no longer needed), then run git submodule
sync and git submodule update --init --recursive to apply the change; if
removing, also remove the src/content entry from .gitmodules and .git/config and
commit the removal (or use git rm --cached src/content) so CI and fresh clones
no longer attempt to fetch the broken remote.

Comment on lines 48 to 55
const { mantecaLimits, bridgeLimits, isLoading, hasMantecaLimits, hasBridgeLimits } = useLimits()
const { isUserMantecaKycApproved, isUserBridgeKycApproved } = useKycStatus()

// normalize currency to valid LimitCurrency type
const currency = mapToLimitCurrency(currencyInput)

// determine if user is "local" (has manteca kyc for latam operations)
// this replaces the external isLocalUser parameter
const isLocalUser = isUserMantecaKycApproved
// determine if user is "local" (has manteca limits = enabled manteca rails)
const isLocalUser = hasMantecaLimits

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't fail open when limits cannot be loaded.

src/hooks/useLimits.ts reports 5xx/network failures via error, not empty limits. With the new hasMantecaLimits / hasBridgeLimits selectors, those failures now look identical to “no applicable rails”, so this hook falls through to a permissive result and can disable client-side limit checks entirely. Please surface an unknown-limits state here instead of treating missing data as no limits.

Also applies to: 256-275

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

In `@src/features/limits/hooks/useLimitsValidation.ts` around lines 48 - 55, The
hook currently treats missing limits (e.g., mantecaLimits/bridgeLimits falsey)
as "no limits" via hasMantecaLimits/hasBridgeLimits and returns a permissive
result; instead detect and surface an unknown-limits state when the underlying
useLimits hook reports an error or when limits are not loaded (isLoading) so we
don't fail open. Update useLimitsValidation (references: mantecaLimits,
bridgeLimits, isLoading, hasMantecaLimits, hasBridgeLimits, isLocalUser,
mapToLimitCurrency) to: 1) read the error/fetch status from useLimits (use the
error or fetch flag exported by useLimits), 2) if error is present or the limits
arrays are undefined/null while not explicitly empty, return/emit an
"unknownLimits" result (or set a boolean like hasUnknownLimits) instead of
treating hasMantecaLimits/hasBridgeLimits as false, and 3) propagate that
unknown state to callers so client-side limit checks can fail-closed; apply the
same change to the similar logic around lines 256-275.

Comment on lines +23 to 24
const { hasMantecaLimits } = useLimits()

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard unlocked-region routing until limits load completes.

hasMantecaLimits is false until limits data resolves, so early clicks can compute a wrong provider route. Consider passing isLoading from useLimits and disabling the click path until settled.

🛠️ Suggested fix
-    const { hasMantecaLimits } = useLimits()
+    const { hasMantecaLimits, isLoading: isLimitsLoading } = useLimits()
...
-                <UnlockedRegionsList regions={unlockedRegions} hasMantecaKyc={hasMantecaLimits} />
+                <UnlockedRegionsList
+                    regions={unlockedRegions}
+                    hasMantecaKyc={hasMantecaLimits}
+                    isLimitsLoading={isLimitsLoading}
+                />
 interface UnlockedRegionsListProps {
     regions: Region[]
     hasMantecaKyc: boolean
+    isLimitsLoading: boolean
 }
 
-const UnlockedRegionsList = ({ regions, hasMantecaKyc }: UnlockedRegionsListProps) => {
+const UnlockedRegionsList = ({ regions, hasMantecaKyc, isLimitsLoading }: UnlockedRegionsListProps) => {
...
                     onClick={() => {
+                        if (isLimitsLoading) return
                         const route = getProviderRoute(region.path, hasMantecaKyc)
                         router.push(route)
                     }}
+                    isDisabled={isLimitsLoading}

Also applies to: 68-69

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

In `@src/features/limits/views/LimitsPageView.tsx` around lines 23 - 24, The
routing decision uses hasMantecaLimits from useLimits before limits finish
loading, causing incorrect provider routes on early clicks; update useLimits to
return an isLoading flag (or consume existing one) and in LimitsPageView guard
click handlers and any routing logic (where hasMantecaLimits is read) by
checking !isLoading first — disable or no-op the click path and/or show a
loading state until isLoading is false, then compute the provider route using
the settled hasMantecaLimits value (update handlers in LimitsPageView that
reference hasMantecaLimits, including the click paths around lines 68-69).

Comment on lines +64 to +87
const isSumsubInProgress = useMemo(() => isSumsubStatusInProgress(sumsubStatus), [sumsubStatus])

const isKycInProgress = useMemo(
() => isBridgeUnderReview || isSumsubInProgress,
[isBridgeUnderReview, isSumsubInProgress]
)

return {
// combined
isKycApproved,
isKycInProgress,
// bridge
isBridgeApproved,
isBridgeUnderReview,
// manteca
isMantecaApproved,
// sumsub
isSumsubApproved,
isSumsubActionRequired,
sumsubStatus,
sumsubRejectLabels,
sumsubRejectType,
sumsubVerificationRegionIntent,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

isSumsubInProgress is computed but not returned.

The hook computes isSumsubInProgress on line 64 but it's not included in the return object. This appears to be an oversight since it would be useful for consumers to know if Sumsub verification is in progress.

🐛 Proposed fix to include isSumsubInProgress in return
     return {
         // combined
         isKycApproved,
         isKycInProgress,
         // bridge
         isBridgeApproved,
         isBridgeUnderReview,
         // manteca
         isMantecaApproved,
         // sumsub
         isSumsubApproved,
         isSumsubActionRequired,
+        isSumsubInProgress,
         sumsubStatus,
         sumsubRejectLabels,
         sumsubRejectType,
         sumsubVerificationRegionIntent,
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useUnifiedKycStatus.ts` around lines 64 - 87, The hook
useUnifiedKycStatus computes isSumsubInProgress (via useMemo) but never returns
it; update the return object of useUnifiedKycStatus to include
isSumsubInProgress (e.g., alongside the other Sumsub fields like
isSumsubApproved and sumsubStatus) so consumers can read Sumsub progress state;
ensure you export it in the same shape as the other flags so existing callers
can destructure it (no other logic changes needed since isKycInProgress already
derives from isSumsubInProgress).

Comment on lines +55 to +57
const latamVerification = [...latamVerifications].sort(
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
)[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential undefined value in sort comparison.

The sort callback uses v.updatedAt directly, but updatedAt may be undefined based on IUserKycVerification interface. new Date(undefined) returns Invalid Date, which can cause incorrect sorting.

🛡️ Proposed fix with fallback
     const latamVerification = [...latamVerifications].sort(
-        (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
+        (a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime()
     )[0]
📝 Committable suggestion

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

Suggested change
const latamVerification = [...latamVerifications].sort(
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
)[0]
const latamVerification = [...latamVerifications].sort(
(a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime()
)[0]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/kyc-grouping.utils.ts` around lines 55 - 57, The sort comparator
for latamVerifications can pass undefined updatedAt (per IUserKycVerification)
into new Date(), producing Invalid Date and breaking ordering; update the
comparator used to compute latamVerification to guard against undefined by
normalizing updatedAt for both a and b (e.g., use a.updatedAt &&
!isNaN(Date.parse(a.updatedAt)) ? Date.parse(a.updatedAt) : 0) so the comparator
always compares numeric timestamps (or filter out entries with no valid
updatedAt before sorting), and ensure you reference the latamVerifications array
and latamVerification assignment when applying the fix.

chip-peanut-bot bot and others added 5 commits March 16, 2026 17:03
Better communicates group bill splitting functionality.
Requested by Konrad.
Pulls in pt-br supported-geographies translation (peanut-content#12)
to fix broken link checker.
Resolves submodule conflict by updating content submodule to latest
(includes pt-br supported-geographies translation).
Rebases icon change on dev's Icon.tsx and resolves submodule conflict.
feat: change split icon to GroupsRounded
@jjramirezn jjramirezn merged commit 5c75d29 into main Mar 17, 2026
9 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.

3 participants