diff --git a/src/app/(mobile-ui)/add-money/[country]/bank/page.tsx b/src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
index 9c99f92cb9..33c56d53c7 100644
--- a/src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
+++ b/src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
@@ -515,7 +515,7 @@ export default function OnrampBankPage() {
message={pendingModal.message}
/>
-
+
diff --git a/src/app/(mobile-ui)/withdraw/manteca/page.tsx b/src/app/(mobile-ui)/withdraw/manteca/page.tsx
index 4501750936..c5d99913fe 100644
--- a/src/app/(mobile-ui)/withdraw/manteca/page.tsx
+++ b/src/app/(mobile-ui)/withdraw/manteca/page.tsx
@@ -664,7 +664,6 @@ function MantecaBankWithdrawFlow() {
onClose={limitIncreaseFlow.handleClose}
onComplete={limitIncreaseFlow.handleSdkComplete}
onRefreshToken={limitIncreaseFlow.refreshToken}
- autoStart
isMultiLevel
/>
void
- onClose: () => void
-}) => {
- return (
-
-
-
-
-
Secure Verification. Limited Data Use.
-
-
- The verification is done using a trusted provider, which shares your verification status with
- Peanut.
-
-
- It operates under industry-standard security and privacy practices.
-
-
Peanut never sees or stores your verification data.
-
-
- Start Secure Verification
-
-
-
- )
-}
-
-export default StartVerificationView
diff --git a/src/components/Global/IframeWrapper/index.tsx b/src/components/Global/IframeWrapper/index.tsx
index 400ea683ab..1565b63345 100644
--- a/src/components/Global/IframeWrapper/index.tsx
+++ b/src/components/Global/IframeWrapper/index.tsx
@@ -3,7 +3,6 @@ import Modal from '../Modal'
import { Icon, type IconName } from '../Icons/Icon'
import ActionModal from '../ActionModal'
import { useRouter } from 'next/navigation'
-import StartVerificationView from './StartVerificationView'
import { useModalsContext } from '@/context/ModalsContext'
import { Button, type ButtonVariant } from '@/components/0_Bruddle/Button'
@@ -12,15 +11,13 @@ export type IFrameWrapperProps = {
visible: boolean
onClose: (source?: 'manual' | 'completed' | 'tos_accepted') => void
closeConfirmMessage?: string
- skipStartView?: boolean
}
-const IframeWrapper = ({ src, visible, onClose, closeConfirmMessage, skipStartView }: IFrameWrapperProps) => {
+const IframeWrapper = ({ src, visible, onClose, closeConfirmMessage }: IFrameWrapperProps) => {
const enableConfirmationPrompt = closeConfirmMessage !== undefined
const [isHelpModalOpen, setIsHelpModalOpen] = useState(false)
const [modalVariant, setModalVariant] = useState<'stop-verification' | 'trouble'>('trouble')
const [copied, setCopied] = useState(false)
- const [isVerificationStarted, setIsVerificationStarted] = useState(skipStartView ?? false)
const router = useRouter()
const { setIsSupportModalOpen } = useModalsContext()
@@ -72,8 +69,8 @@ const IframeWrapper = ({ src, visible, onClose, closeConfirmMessage, skipStartVi
}
return {
- title: 'Exit and lose progress?',
- description: 'If you exit now, you’ll need to start the ID check again from scratch.',
+ title: 'Exit for now?',
+ description: 'You can come back and finish this anytime.',
icon: 'alert' as IconName,
iconContainerClassName: 'bg-secondary-1',
ctas: [
@@ -135,49 +132,42 @@ const IframeWrapper = ({ src, visible, onClose, closeConfirmMessage, skipStartVi
preventClose={true}
hideOverlay={false}
>
- {!isVerificationStarted ? (
- onClose('manual')}
- onStartVerification={() => setIsVerificationStarted(true)}
- />
- ) : (
-
-
-
-
-
{
- setModalVariant('stop-verification')
- setIsHelpModalOpen(true)
- }}
- shadowType="primary"
- >
- Stop verification process
-
+
+
+
+
+
{
+ setModalVariant('stop-verification')
+ setIsHelpModalOpen(true)
+ }}
+ shadowType="primary"
+ >
+ Stop verification process
+
-
{
- setModalVariant('trouble')
- setIsHelpModalOpen(true)
- }}
- className="flex items-center gap-1"
- >
-
- Having trouble?
-
-
+
{
+ setModalVariant('trouble')
+ setIsHelpModalOpen(true)
+ }}
+ className="flex items-center gap-1"
+ >
+
+ Having trouble?
+
- )}
+
setIsHelpModalOpen(false)}
diff --git a/src/components/Kyc/BridgeTosStep.tsx b/src/components/Kyc/BridgeTosStep.tsx
index 4f71683068..44c4e1aad0 100644
--- a/src/components/Kyc/BridgeTosStep.tsx
+++ b/src/components/Kyc/BridgeTosStep.tsx
@@ -136,7 +136,7 @@ export const BridgeTosStep = ({ visible, onComplete, onSkip, reasonCode }: Bridg
]}
/>
- {tosLink && }
+ {tosLink && }
>
)
}
diff --git a/src/components/Kyc/KycStatusDrawer.tsx b/src/components/Kyc/KycStatusDrawer.tsx
index 7f64e4222e..f538d54972 100644
--- a/src/components/Kyc/KycStatusDrawer.tsx
+++ b/src/components/Kyc/KycStatusDrawer.tsx
@@ -89,7 +89,7 @@ export const KycStatusDrawer = ({ isOpen, onClose, onKeepMounted }: KycStatusDra
{sumsubFlow.error && {sumsubFlow.error}
}
-
+
>
)
}
diff --git a/src/components/Kyc/SumsubKycModals.tsx b/src/components/Kyc/SumsubKycModals.tsx
index 7d413c61ca..c081b04996 100644
--- a/src/components/Kyc/SumsubKycModals.tsx
+++ b/src/components/Kyc/SumsubKycModals.tsx
@@ -5,7 +5,6 @@ import { type useMultiPhaseKycFlow } from '@/hooks/useMultiPhaseKycFlow'
interface SumsubKycModalsProps {
flow: ReturnType
- autoStartSdk?: boolean
}
/**
@@ -15,7 +14,7 @@ interface SumsubKycModalsProps {
*
* pair with useMultiPhaseKycFlow hook for the logic.
*/
-export const SumsubKycModals = ({ flow, autoStartSdk }: SumsubKycModalsProps) => {
+export const SumsubKycModals = ({ flow }: SumsubKycModalsProps) => {
return (
<>
onClose={flow.handleSdkClose}
onComplete={flow.handleSdkComplete}
onRefreshToken={flow.refreshToken}
- autoStart={autoStartSdk}
isMultiLevel={flow.isMultiLevel}
/>
@@ -42,12 +40,7 @@ export const SumsubKycModals = ({ flow, autoStartSdk }: SumsubKycModalsProps) =>
/>
{flow.tosLink && (
-
+
)}
>
)
diff --git a/src/components/Kyc/SumsubKycWrapper.tsx b/src/components/Kyc/SumsubKycWrapper.tsx
index a23b4834d5..e6b0b5d73e 100644
--- a/src/components/Kyc/SumsubKycWrapper.tsx
+++ b/src/components/Kyc/SumsubKycWrapper.tsx
@@ -5,8 +5,8 @@ import Modal from '@/components/Global/Modal'
import ActionModal from '@/components/Global/ActionModal'
import { Icon, type IconName } from '@/components/Global/Icons/Icon'
import { Button, type ButtonVariant } from '@/components/0_Bruddle/Button'
+import Loading from '@/components/Global/Loading'
import { useModalsContext } from '@/context/ModalsContext'
-import StartVerificationView from '../Global/IframeWrapper/StartVerificationView'
import { evaluateSumsubStatusEvent, type SumsubStatusEventPayload } from './sumsubStatusEvent.utils'
// todo: move to consts
@@ -19,8 +19,6 @@ interface SumsubKycWrapperProps {
onComplete: () => void
onError?: (error: unknown) => void
onRefreshToken: () => Promise
- /** skip StartVerificationView and launch SDK immediately (for re-submissions) */
- autoStart?: boolean
/** multi-level workflow (e.g. LATAM) — don't close SDK on Level 1 submission */
isMultiLevel?: boolean
}
@@ -32,10 +30,8 @@ export const SumsubKycWrapper = ({
onComplete,
onError,
onRefreshToken,
- autoStart,
isMultiLevel,
}: SumsubKycWrapperProps) => {
- const [isVerificationStarted, setIsVerificationStarted] = useState(false)
const [sdkLoaded, setSdkLoaded] = useState(false)
const [sdkLoadError, setSdkLoadError] = useState(false)
const [isHelpModalOpen, setIsHelpModalOpen] = useState(false)
@@ -68,27 +64,46 @@ export const SumsubKycWrapper = ({
// load sumsub websdk script
useEffect(() => {
+ if (window.snsWebSdk) {
+ setSdkLoaded(true)
+ return undefined
+ }
+
+ const handleLoaded = () => setSdkLoaded(true)
+ const handleError = () => {
+ console.error('[sumsub] failed to load websdk script')
+ setSdkLoadError(true)
+ }
+
const existingScript = document.getElementById('sumsub-websdk')
if (existingScript) {
- setSdkLoaded(true)
- return
+ // another wrapper instance appended the script and it's still
+ // downloading — a bare existence check would init against an
+ // undefined window.snsWebSdk
+ existingScript.addEventListener('load', handleLoaded)
+ existingScript.addEventListener('error', handleError)
+ // the script may have finished between the snsWebSdk check above
+ // and the listener attach — re-check so we don't wait forever
+ if (window.snsWebSdk) handleLoaded()
+ return () => {
+ existingScript.removeEventListener('load', handleLoaded)
+ existingScript.removeEventListener('error', handleError)
+ }
}
const script = document.createElement('script')
script.id = 'sumsub-websdk'
script.src = SUMSUB_SDK_URL
script.async = true
- script.onload = () => setSdkLoaded(true)
- script.onerror = () => {
- console.error('[sumsub] failed to load websdk script')
- setSdkLoadError(true)
- }
+ script.onload = handleLoaded
+ script.onerror = handleError
document.head.appendChild(script)
+ return undefined
}, [])
- // initialize sdk when verification starts and all deps are ready
+ // initialize sdk as soon as the modal is visible and all deps are ready
useEffect(() => {
- if (!isVerificationStarted || !accessToken || !sdkLoaded || !sdkContainerRef.current) return
+ if (!visible || !accessToken || !sdkLoaded || !sdkContainerRef.current) return
// clean up previous instance
if (sdkInstanceRef.current) {
@@ -197,6 +212,8 @@ export const SumsubKycWrapper = ({
}
} catch (error) {
console.error('[sumsub] failed to initialize sdk', error)
+ // surface the error UI — without this the modal stays blank
+ setSdkLoadError(true)
stableOnError(error)
}
@@ -211,27 +228,16 @@ export const SumsubKycWrapper = ({
sdkInstanceRef.current = null
}
}
- }, [isVerificationStarted, accessToken, sdkLoaded, stableOnComplete, stableOnError, stableOnRefreshToken])
+ }, [visible, accessToken, sdkLoaded, stableOnComplete, stableOnError, stableOnRefreshToken])
- // reset state when modal closes, auto-start on re-submission
+ // reset state when modal closes (the init effect's cleanup already
+ // destroys the SDK instance — visible is one of its deps)
useEffect(() => {
if (!visible) {
- setIsVerificationStarted(false)
setSdkLoadError(false)
hasSubmittedRef.current = false
- if (sdkInstanceRef.current) {
- try {
- sdkInstanceRef.current.destroy()
- } catch {
- // ignore cleanup errors
- }
- sdkInstanceRef.current = null
- }
- } else if (autoStart) {
- // skip StartVerificationView on re-submission (user already consented)
- setIsVerificationStarted(true)
}
- }, [visible, autoStart])
+ }, [visible])
// Close-button handler. After the user has submitted, the "are you sure
// you want to stop?" modal is misleading — they're done, not abandoning.
@@ -269,54 +275,30 @@ export const SumsubKycWrapper = ({
}
}
- return autoStart
- ? {
- title: 'Exit for now?',
- description: 'You can pick up where you left off later — your progress is saved.',
- icon: 'alert' as IconName,
- iconContainerClassName: 'bg-secondary-1',
- ctas: [
- {
- text: 'Exit',
- onClick: () => {
- setIsHelpModalOpen(false)
- onClose()
- },
- variant: 'purple' as ButtonVariant,
- shadowSize: '4' as const,
- },
- {
- text: 'Continue',
- onClick: () => setIsHelpModalOpen(false),
- variant: 'transparent' as ButtonVariant,
- className: 'underline text-sm font-medium w-full h-fit mt-3',
- },
- ],
- }
- : {
- title: 'Exit and lose progress?',
- description: "If you exit now, you'll need to start the ID check again from scratch.",
- icon: 'alert' as IconName,
- iconContainerClassName: 'bg-secondary-1',
- ctas: [
- {
- text: 'Exit',
- onClick: () => {
- setIsHelpModalOpen(false)
- onClose()
- },
- variant: 'purple' as ButtonVariant,
- shadowSize: '4' as const,
- },
- {
- text: 'Keep going',
- onClick: () => setIsHelpModalOpen(false),
- variant: 'transparent' as ButtonVariant,
- className: 'underline text-sm font-medium w-full h-fit mt-3',
- },
- ],
- }
- }, [autoStart, modalVariant, onClose, setIsSupportModalOpen])
+ return {
+ title: 'Exit for now?',
+ description: 'You can pick up where you left off later — your progress is saved.',
+ icon: 'alert' as IconName,
+ iconContainerClassName: 'bg-secondary-1',
+ ctas: [
+ {
+ text: 'Exit',
+ onClick: () => {
+ setIsHelpModalOpen(false)
+ onClose()
+ },
+ variant: 'purple' as ButtonVariant,
+ shadowSize: '4' as const,
+ },
+ {
+ text: 'Continue',
+ onClick: () => setIsHelpModalOpen(false),
+ variant: 'transparent' as ButtonVariant,
+ className: 'underline text-sm font-medium w-full h-fit mt-3',
+ },
+ ],
+ }
+ }, [modalVariant, onClose, setIsSupportModalOpen])
return (
<>
@@ -331,12 +313,7 @@ export const SumsubKycWrapper = ({
preventClose={true}
hideOverlay={false}
>
- {!isVerificationStarted ? (
- setIsVerificationStarted(true)}
- />
- ) : sdkLoadError ? (
+ {sdkLoadError ? (
@@ -362,7 +339,16 @@ export const SumsubKycWrapper = ({
-
+
+ {/* sits behind the SDK iframe — covered once it paints */}
+
+
+
+
+
)}
diff --git a/src/components/Profile/views/UnlockedRegions.view.tsx b/src/components/Profile/views/UnlockedRegions.view.tsx
index 7660728c59..bd9eed9483 100644
--- a/src/components/Profile/views/UnlockedRegions.view.tsx
+++ b/src/components/Profile/views/UnlockedRegions.view.tsx
@@ -96,8 +96,6 @@ const UnlockedRegions = () => {
// persist region intent for the duration of the kyc session so token refresh
// and status checks use the correct template after the confirmation modal closes
const [activeRegionIntent, setActiveRegionIntent] = useState
(undefined)
- // skip StartVerificationView when re-submitting (user already consented)
- const [autoStartSdk, setAutoStartSdk] = useState(false)
// when an initiate fails, flow.error is set but the modal that triggered it
// has already closed — show a dismissible error modal so the user isn't left
// staring at a screen where "verify now" appeared to do nothing.
@@ -147,7 +145,6 @@ const UnlockedRegions = () => {
const handleFinalKycSuccess = useCallback(() => {
setSelectedRegion(null)
setActiveRegionIntent(undefined)
- setAutoStartSdk(false)
}, [])
const flow = useMultiPhaseKycFlow({
@@ -156,7 +153,6 @@ const UnlockedRegions = () => {
onManualClose: () => {
setSelectedRegion(null)
setActiveRegionIntent(undefined)
- setAutoStartSdk(false)
},
})
@@ -195,12 +191,6 @@ const UnlockedRegions = () => {
await flow.handleInitiateKyc(intent, undefined, true)
}, [flow.handleInitiateKyc, selectedRegion, hasCardAccess, hasActiveCard, router])
- // re-submission: skip StartVerificationView since user already consented
- const handleResubmitKyc = useCallback(async () => {
- setAutoStartSdk(true)
- await handleStartKyc()
- }, [handleStartKyc])
-
// ROW (rest-of-world) regions have no provider/rail, so an initiate there is a
// terminal "not available in your region yet" — not a transient failure. Only
// offer "Try again" for regions that can actually succeed on a retry.
@@ -249,7 +239,7 @@ const UnlockedRegions = () => {
@@ -257,7 +247,7 @@ const UnlockedRegions = () => {
{
}
/>
-
+
)
}
diff --git a/src/features/limits/components/IncreaseLimitsButton.tsx b/src/features/limits/components/IncreaseLimitsButton.tsx
index d363874643..3cc9c6ee39 100644
--- a/src/features/limits/components/IncreaseLimitsButton.tsx
+++ b/src/features/limits/components/IncreaseLimitsButton.tsx
@@ -65,7 +65,6 @@ export default function IncreaseLimitsButton() {
onClose={actionFlow.handleClose}
onComplete={actionFlow.handleSdkComplete}
onRefreshToken={actionFlow.refreshToken}
- autoStart
isMultiLevel
/>
>