feat: kyc 2.0 frontend#1679
Conversation
…on and kyc flow states
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Sumsub KYC end-to-end: server action and types, WebSDK typings, new hook, WebSocket event handling, SDK wrapper and UI flows; centralizes KYC status predicates; updates hooks/interfaces to include Sumsub; removes legacy identity-verification pages/components and simplifies verification layout; minor text and small-export removals. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@src/app/actions/sumsub.ts`:
- Line 15: Check the jwt token returned by getJWTCookie() before using it: after
const jwtToken = (await getJWTCookie())?.value, verify jwtToken is a non-empty
string and if not throw or return a clear error (e.g., "Missing JWT for Sumsub
action") instead of allowing "Bearer undefined" in the Authorization header;
apply the same validation where the token is read later (the block around lines
30-33) so all usages of jwtToken in this module are protected.
In `@src/components/Global/IframeWrapper/StartVerificationView.tsx`:
- Around line 35-39: The privacy line "only shares a yes/no with Peanut" is
inaccurate given Sumsub returns multiple states (NOT_STARTED, PENDING,
IN_REVIEW, APPROVED, REJECTED, ACTION_REQUIRED) and rejectLabels are persisted
on the user's KYC verification record; update the copy in StartVerificationView
(the <p> elements currently stating verification details) to accurately reflect
what is shared—for example, change to wording such as "shares your verification
status and any relevant feedback with Peanut" or otherwise soften the claim so
it does not assert a binary-only payload; ensure the new copy aligns with the
data stored (status and rejectLabels) and appears where the three <p> elements
are rendered.
In `@src/components/Kyc/KycFlow.tsx`:
- Around line 5-12: KycFlow's props currently omit the optional callbacks
accepted by SumsubKycFlow; update the KycFlowProps interface to include optional
onKycSuccess and onManualClose properties with the same signatures/types used by
SumsubKycFlow (or import the prop types from SumsubKycFlow to keep them in
sync), then forward those props in the component JSX (i.e., pass
onKycSuccess={onKycSuccess} and onManualClose={onManualClose} into
<SumsubKycFlow ... />) so callers can subscribe to KYC completion and manual
close events.
In `@src/components/Profile/views/RegionsVerification.view.tsx`:
- Around line 8-52: regionIntent is lost because it's derived from
selectedRegion which is cleared when starting KYC; persist the intent in state
for the active session instead: add a new state (e.g., sessionRegionIntent) and
set it inside handleStartKyc (or when user confirms start) using the current
selectedRegion.path, pass sessionRegionIntent to useSumsubKycFlow instead of
deriving regionIntent from selectedRegion, and clear sessionRegionIntent in the
same callbacks that end the flow (onKycSuccess, onManualClose,
handleSumsubClose, closeVerificationProgressModal) so the intent remains stable
for token refreshes and status checks until the flow completes.
In `@src/constants/kyc.consts.ts`:
- Around line 1-59: KycStatusCategory and getKycStatusCategory are using
different vocabularies; update the KycStatusCategory type to match the helper's
outputs (change from 'approved'|'pending'|'failed' to
'completed'|'processing'|'failed') and change getKycStatusCategory's return type
to use KycStatusCategory so the type and implementation are consistent; ensure
any other usages of KycStatusCategory across the codebase are updated to the new
labels if necessary.
In `@src/hooks/useIdentityVerification.tsx`:
- Around line 174-178: Update the Bridge QR-only logic to avoid adding redundant
regions when Sumsub already grants full LATAM access: in the conditional that
currently reads using isBridgeApproved and !isMantecaApproved, include a check
for !isSumsubApproved so it becomes isBridgeApproved && !isMantecaApproved &&
!isSumsubApproved; this ensures you only push MANTECA_QR_ONLY_REGIONS and
BRIDGE_SUPPORTED_LATAM_COUNTRIES into unlocked when the user lacks both Manteca
and Sumsub approvals (refer to symbols isBridgeApproved, isMantecaApproved,
isSumsubApproved, unlocked, MANTECA_QR_ONLY_REGIONS,
BRIDGE_SUPPORTED_LATAM_COUNTRIES).
In `@src/hooks/useUnifiedKycStatus.ts`:
- Around line 26-29: The sumsubVerification logic in useUnifiedKycStatus uses
.find() which may pick an older SUMSUB entry; change it to either mirror
useQrKycGate.ts by using .some() when you only need to know if any SUMSUB
verification exists, or (preferably) select the most recent SUMSUB verification
by inspecting user?.user.kycVerifications and picking the entry with provider
=== 'SUMSUB' and the latest updatedAt/createdAt timestamp (e.g., via
Array.prototype.reduce or a timestamp-based sort) so sumsubVerification reflects
the newest attempt.
In `@src/hooks/useWebSocket.ts`:
- Around line 65-82: The dependency array in useWebSocket is malformed: remove
the stray trailing comma and extra blank lines so the array is a well-formed
single list of dependencies (e.g., [onHistoryEntry, onKycStatusUpdate,
onMantecaKycStatusUpdate, onSumsubKycStatusUpdate, onTosUpdate, onPendingPerk,
onConnect, onDisconnect, onError]) and tidy spacing/commas; while here ensure
each handler referenced (onHistoryEntry, onKycStatusUpdate,
onMantecaKycStatusUpdate, onSumsubKycStatusUpdate, onTosUpdate, onPendingPerk,
onConnect, onDisconnect, onError) is the intended dependency (or wrapped with
useCallback elsewhere) so the effect dependency list is syntactically correct
and consistent.
🧹 Nitpick comments (5)
src/components/Kyc/SumsubKycWrapper.tsx (3)
11-12: Address TODO: Move SDK URL to constants.The TODO comment indicates this URL should be centralized. Consider moving it to a constants file for consistency with other external URLs in the codebase.
Would you like me to open an issue to track moving this constant?
56-74: Script element is never removed on unmount.The dynamically created script element is appended to
document.headbut is never cleaned up when the component unmounts. While this is typically acceptable for SDK scripts (they're often designed to persist), it's worth noting that:
- If the component mounts/unmounts rapidly, the
onloadcallback could fire after unmount, potentially causing state updates on an unmounted component.- The script persists in the DOM even after the wrapper is no longer needed.
Consider adding a cleanup function or using an
isMountedref to guard against state updates after unmount.♻️ Proposed fix to guard against unmounted state updates
// load sumsub websdk script useEffect(() => { + let isMounted = true const existingScript = document.getElementById('sumsub-websdk') if (existingScript) { setSdkLoaded(true) return } const script = document.createElement('script') script.id = 'sumsub-websdk' script.src = SUMSUB_SDK_URL script.async = true - script.onload = () => setSdkLoaded(true) + script.onload = () => { + if (isMounted) setSdkLoaded(true) + } script.onerror = () => { console.error('[sumsub] failed to load websdk script') - setSdkLoadError(true) + if (isMounted) setSdkLoadError(true) } document.head.appendChild(script) + + return () => { + isMounted = false + } }, [])
219-246: Fixed height percentages may cause layout issues.The SDK container uses
style={{ height: '85%' }}and the controls useh-[12%]. This leaves 3% unaccounted for, which could cause overflow or gaps on some screen sizes. Consider using flexbox withflex-growinstead of fixed percentages for more robust layout.src/hooks/useIdentityVerification.tsx (1)
79-82: Case-sensitive region path matching.The
getRegionIntentfunction only matches'latam'exactly (lowercase). IfregionPathis'LATAM'or'Latam', it will return'STANDARD'instead of'LATAM'. Consider normalizing the input:♻️ Proposed fix for case-insensitive matching
/** maps a region path to the sumsub kyc template intent */ export const getRegionIntent = (regionPath: string): KYCRegionIntent => { - return regionPath === 'latam' ? 'LATAM' : 'STANDARD' + return regionPath.toLowerCase() === 'latam' ? 'LATAM' : 'STANDARD' }src/hooks/useUnifiedKycStatus.ts (1)
33-33: Unsafe type cast forsumsubStatus.The status is cast to
SumsubKycStatuswithout validation. If the backend returns an unexpected status string, this could cause issues downstream. Consider adding validation or keeping the type asstring | null.♻️ Option to validate status
+const VALID_SUMSUB_STATUSES = new Set(['NOT_STARTED', 'PENDING', 'IN_REVIEW', 'APPROVED', 'REJECTED', 'ACTION_REQUIRED']) + const sumsubStatus = useMemo(() => { - return (sumsubVerification?.status as SumsubKycStatus) ?? null + const status = sumsubVerification?.status + if (status && VALID_SUMSUB_STATUSES.has(status)) { + return status as SumsubKycStatus + } + return null }, [sumsubVerification])
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/useIdentityVerification.tsx (1)
127-143:⚠️ Potential issue | 🟠 MajorSumsub-approved users still fail
isVerifiedForCountry.
useMemonow unlocks all regions for Sumsub, butisVerifiedForCountrystill only checks Bridge/Manteca. If any flow uses this helper, Sumsub-approved users can be treated as unverified.✅ Suggested fix
const isVerifiedForCountry = useCallback( (code: string) => { + if (isUserSumsubKycApproved) return true const upper = code.toUpperCase() // Check if user has active Manteca verification for this specific country const mantecaActive = user?.user.kycVerifications?.some( (v) => v.provider === 'MANTECA' && (v.mantecaGeo || '').toUpperCase() === upper && v.status === MantecaKycStatus.ACTIVE ) ?? false // Manteca countries need country-specific verification, others just need Bridge KYC return isMantecaSupportedCountry(upper) ? mantecaActive : isUserBridgeKycApproved }, - [user, isUserBridgeKycApproved, isMantecaSupportedCountry] + [user, isUserBridgeKycApproved, isUserSumsubKycApproved, isMantecaSupportedCountry] )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useIdentityVerification.tsx` around lines 127 - 143, isVerifiedForCountry currently only considers Manteca and Bridge KYC causing Sumsub-approved users to be treated as unverified; add a Sumsub approval check and include it in the non-Manteca branch. Inside isVerifiedForCountry compute something like sumsubApproved = user?.user.kycVerifications?.some(v => v.provider === 'SUMSUB' && v.status === SumsubKycStatus.APPROVED) ?? false, then return isMantecaSupportedCountry(upper) ? mantecaActive : (isUserBridgeKycApproved || sumsubApproved); reference the isVerifiedForCountry function, isMantecaSupportedCountry, isUserBridgeKycApproved and user?.user.kycVerifications when making the change.
🧹 Nitpick comments (2)
src/components/Profile/views/RegionsVerification.view.tsx (1)
99-117: Please resolve the TODO before release.Leaving TODOs in UI copy tends to be forgotten; consider updating the modal copy now or tracking it in an issue.
Want me to draft the updated modal copy or open a tracking issue?
🤖 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 99 - 117, Remove the inline TODO above the ActionModal and update the modal copy to reflect the benefits of unlocking a region instead of leaving a developer note; specifically edit the title/description used by ActionModal (title: `Unlock ${selectedRegion?.name ?? ''}` and description prop) to clearly state benefits (e.g., "Verify your identity to send and receive money, access local currency support, and enable faster payouts") and keep the CTA tied to handleStartKyc/isLoading, or if you cannot update copy now create a tracked issue and replace the TODO with a brief comment referencing that issue ID; ensure changes are applied where ActionModal, selectedRegion, handleStartKyc, and handleModalClose are used.src/hooks/useIdentityVerification.tsx (1)
79-82: NormalizeregionPathcasing for resilience.If this ever comes from URL params, mixed casing will map to
STANDARD. Lowercasing avoids surprises.♻️ Suggested tweak
export const getRegionIntent = (regionPath: string): KYCRegionIntent => { - return regionPath === 'latam' ? 'LATAM' : 'STANDARD' + return regionPath.toLowerCase() === 'latam' ? 'LATAM' : 'STANDARD' }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useIdentityVerification.tsx` around lines 79 - 82, getRegionIntent currently compares regionPath verbatim which misclassifies mixed-case input; normalize the input first (e.g., trim() and toLowerCase()) before comparing so values like "LatAm" or " LATAM " map to 'LATAM'. Update the getRegionIntent function to canonicalize regionPath and then return KYCRegionIntent 'LATAM' when the normalized value equals 'latam', otherwise return 'STANDARD'.
🤖 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/Profile/views/RegionsVerification.view.tsx`:
- Around line 49-63: The current flow sets activeRegionIntent state then
immediately awaits handleInitiateKyc, causing a race where handleInitiateKyc
reads the old intent; update the hook useSumsubKycFlow so its initiate function
(handleInitiateKyc) accepts an optional intent parameter (e.g.,
handleInitiateKyc(intent?: RegionIntent)) and uses that intent if provided, and
then modify RegionsVerification.view.tsx's handleStartKyc to compute const
intent = getRegionIntent(selectedRegion.path), call
setActiveRegionIntent(intent) and then await handleInitiateKyc(intent) (instead
of relying on state); also update any other call sites of handleInitiateKyc to
pass intent where appropriate.
In `@src/constants/kyc.consts.ts`:
- Around line 21-35: ACTION_REQUIRED is currently present in both
FAILED_STATUSES and SUMSUB_IN_PROGRESS_STATUSES causing conflicting checks
(e.g., isKycStatusFailed vs isSumsubStatusInProgress); decide its intended
classification and remove it from the other set — for example, if
ACTION_REQUIRED should be considered "in progress" move/keep it only in
SUMSUB_IN_PROGRESS_STATUSES and delete it from FAILED_STATUSES (update the
ReadonlySet literal for FAILED_STATUSES and any related tests or docs); ensure
references like isKycStatusFailed and isSumsubStatusInProgress now return
mutually exclusive results for ACTION_REQUIRED.
---
Outside diff comments:
In `@src/hooks/useIdentityVerification.tsx`:
- Around line 127-143: isVerifiedForCountry currently only considers Manteca and
Bridge KYC causing Sumsub-approved users to be treated as unverified; add a
Sumsub approval check and include it in the non-Manteca branch. Inside
isVerifiedForCountry compute something like sumsubApproved =
user?.user.kycVerifications?.some(v => v.provider === 'SUMSUB' && v.status ===
SumsubKycStatus.APPROVED) ?? false, then return isMantecaSupportedCountry(upper)
? mantecaActive : (isUserBridgeKycApproved || sumsubApproved); reference the
isVerifiedForCountry function, isMantecaSupportedCountry,
isUserBridgeKycApproved and user?.user.kycVerifications when making the change.
---
Nitpick comments:
In `@src/components/Profile/views/RegionsVerification.view.tsx`:
- Around line 99-117: Remove the inline TODO above the ActionModal and update
the modal copy to reflect the benefits of unlocking a region instead of leaving
a developer note; specifically edit the title/description used by ActionModal
(title: `Unlock ${selectedRegion?.name ?? ''}` and description prop) to clearly
state benefits (e.g., "Verify your identity to send and receive money, access
local currency support, and enable faster payouts") and keep the CTA tied to
handleStartKyc/isLoading, or if you cannot update copy now create a tracked
issue and replace the TODO with a brief comment referencing that issue ID;
ensure changes are applied where ActionModal, selectedRegion, handleStartKyc,
and handleModalClose are used.
In `@src/hooks/useIdentityVerification.tsx`:
- Around line 79-82: getRegionIntent currently compares regionPath verbatim
which misclassifies mixed-case input; normalize the input first (e.g., trim()
and toLowerCase()) before comparing so values like "LatAm" or " LATAM " map to
'LATAM'. Update the getRegionIntent function to canonicalize regionPath and then
return KYCRegionIntent 'LATAM' when the normalized value equals 'latam',
otherwise return 'STANDARD'.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/constants/kyc.consts.ts (2)
46-51: Consider handlingNOT_STARTEDexplicitly or aligning parameter types.Two observations:
getKycStatusCategorydoesn't explicitly handleNOT_STARTED_STATUSES, returning'processing'for them. If callers rely onisKycStatusNotStartedto skip rendering, this may be fine, but consider documenting this behavior or adding a fourth category.This function takes
stringwhile other helpers acceptstring | undefined | null. For consistency, consider aligning the signature or documenting why this function requires a defined value.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/constants/kyc.consts.ts` around lines 46 - 51, getKycStatusCategory currently treats all non-approved/failed statuses as 'processing' and requires a non-null string; update it to explicitly handle NOT_STARTED_STATUSES (returning a distinct category like 'not_started' or documented behavior) and align the parameter type to accept string | undefined | null (or handle undefined/null at top of getKycStatusCategory) so callers like isKycStatusNotStarted are consistent; adjust checks to use APPROVED_STATUSES, FAILED_STATUSES, NOT_STARTED_STATUSES and return 'completed'|'failed'|'not_started'|'processing' (or add a comment explaining why null/undefined is disallowed) to keep behavior clear and consistent.
8-8: Type union withstringprovides no compile-time narrowing.Since
stringis a supertype, the unionMantecaKycStatus | SumsubKycStatus | stringcollapses to juststring, meaning callers won't get type narrowing benefits. If the intent is documentation, consider using a branded type or removingstringto enforce known statuses at compile time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/constants/kyc.consts.ts` at line 8, The union type KycVerificationStatus currently includes `string`, which collapses the union into a plain string and prevents compile-time narrowing; update the declaration of KycVerificationStatus by removing the raw `string` so it reads `export type KycVerificationStatus = MantecaKycStatus | SumsubKycStatus`, or if you need to allow arbitrary strings, replace with a branded/string-literal approach (e.g., define a Brand like `type UnknownKyc = string & { __brand?: 'UnknownKyc' }` and use `MantecaKycStatus | SumsubKycStatus | UnknownKyc`) and update all usages of KycVerificationStatus accordingly to preserve type safety.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/constants/kyc.consts.ts`:
- Around line 14-25: ACTION_REQUIRED is present in both FAILED_STATUSES and
SUMSUB_IN_PROGRESS_STATUSES causing isKycStatusFailed('ACTION_REQUIRED') and
isSumsubStatusInProgress('ACTION_REQUIRED') to both be true; remove
'ACTION_REQUIRED' from FAILED_STATUSES so it is treated as in-progress for
sumsub. Update the constant FAILED_STATUSES (remove 'ACTION_REQUIRED') and
run/update any tests or logic that reference isKycStatusFailed,
isSumsubStatusInProgress, FAILED_STATUSES, or SUMSUB_IN_PROGRESS_STATUSES to
reflect the new classification.
---
Nitpick comments:
In `@src/constants/kyc.consts.ts`:
- Around line 46-51: getKycStatusCategory currently treats all
non-approved/failed statuses as 'processing' and requires a non-null string;
update it to explicitly handle NOT_STARTED_STATUSES (returning a distinct
category like 'not_started' or documented behavior) and align the parameter type
to accept string | undefined | null (or handle undefined/null at top of
getKycStatusCategory) so callers like isKycStatusNotStarted are consistent;
adjust checks to use APPROVED_STATUSES, FAILED_STATUSES, NOT_STARTED_STATUSES
and return 'completed'|'failed'|'not_started'|'processing' (or add a comment
explaining why null/undefined is disallowed) to keep behavior clear and
consistent.
- Line 8: The union type KycVerificationStatus currently includes `string`,
which collapses the union into a plain string and prevents compile-time
narrowing; update the declaration of KycVerificationStatus by removing the raw
`string` so it reads `export type KycVerificationStatus = MantecaKycStatus |
SumsubKycStatus`, or if you need to allow arbitrary strings, replace with a
branded/string-literal approach (e.g., define a Brand like `type UnknownKyc =
string & { __brand?: 'UnknownKyc' }` and use `MantecaKycStatus | SumsubKycStatus
| UnknownKyc`) and update all usages of KycVerificationStatus accordingly to
preserve type safety.
…e multi phase kyc verification tackling bridge tos acceptance
adds requires_documents display status to rail tracking so bridge rails needing extra documents are distinguished from those needing ToS acceptance. new KycRequiresDocuments component shows human-readable requirement descriptions and a submit button that opens the sumsub SDK with the peanut-additional-docs level. wires into KycStatusDrawer to show the additional docs UI when bridge rails have REQUIRES_EXTRA_INFORMATION status, and extends initiateSumsubKyc to accept a levelName parameter.
- fix icon name: document -> docs, remove unsafe IconName cast - preserve levelName across token refresh via levelNameRef - add explicit additionalRequirements type to IUserRail.metadata - fix needsAdditionalDocs: derive from rail status, not empty requirements - add fallback UI when requirements array is empty
…al states, format - aggregate additionalRequirements across all bridge rails with Array.isArray guard - don't let additional-docs view mask failed/action_required kyc states - run prettier on KycRequiresDocuments
…ubmission feat: bridge additional document collection UI
feat: kyc2.0 status handling and bug fixes
No description provided.