Skip to content

Commit 5c75d29

Browse files
authored
Merge pull request #1752 from peanutprotocol/dev
chore: prod release 134 fe
2 parents 2498c8a + 242d515 commit 5c75d29

101 files changed

Lines changed: 3628 additions & 2159 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

instrumentation-client.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,5 @@
11
import posthog from 'posthog-js'
22

3-
/**
4-
* Strips sensitive parameters from URLs before sending to PostHog.
5-
* Claim link passwords (p=) in hash fragments could be used to steal funds.
6-
* Also strips from query params and referrer as defense-in-depth.
7-
*/
8-
function sanitizeUrl(url: string): string {
9-
if (!url) return url
10-
try {
11-
const parsed = new URL(url, window.location.origin)
12-
// Strip 'p' from hash fragment (claim link password)
13-
if (parsed.hash) {
14-
const hashContent = parsed.hash.slice(1) // remove leading #
15-
const hashParams = new URLSearchParams(hashContent)
16-
if (hashParams.has('p')) {
17-
hashParams.set('p', 'REDACTED')
18-
parsed.hash = '#' + hashParams.toString()
19-
}
20-
}
21-
// Defense-in-depth: also strip from query params
22-
if (parsed.searchParams.has('p')) {
23-
parsed.searchParams.set('p', 'REDACTED')
24-
}
25-
return parsed.toString()
26-
} catch {
27-
// Fallback regex if URL parsing fails
28-
return url.replace(/([#?&])p=[^&#]*/g, '$1p=REDACTED')
29-
}
30-
}
31-
32-
/** URL property keys that PostHog may capture */
33-
const URL_PROPERTIES = ['$current_url', '$pathname', '$referrer', '$initial_referrer'] as const
34-
353
if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'development') {
364
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
375
api_host: '/ingest',
@@ -40,13 +8,5 @@ if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'development') {
408
capture_pageview: true,
419
capture_pageleave: true,
4210
autocapture: true,
43-
sanitize_properties: (properties, _event) => {
44-
for (const key of URL_PROPERTIES) {
45-
if (typeof properties[key] === 'string') {
46-
properties[key] = sanitizeUrl(properties[key])
47-
}
48-
}
49-
return properties
50-
},
5111
})
5212
}

src/app/(mobile-ui)/add-money/[country]/bank/page.tsx

Lines changed: 45 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,32 @@ import { useOnrampFlow } from '@/context/OnrampFlowContext'
99
import { useWallet } from '@/hooks/wallet/useWallet'
1010
import { formatAmount } from '@/utils/general.utils'
1111
import { countryData } from '@/components/AddMoney/consts'
12-
import { type BridgeKycStatus } from '@/utils/bridge-accounts.utils'
13-
import { useWebSocket } from '@/hooks/useWebSocket'
1412
import { useAuth } from '@/context/authContext'
13+
import useKycStatus from '@/hooks/useKycStatus'
1514
import { useCreateOnramp } from '@/hooks/useCreateOnramp'
1615
import { useRouter, useParams } from 'next/navigation'
17-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
16+
import { useCallback, useEffect, useMemo, useState } from 'react'
1817
import countryCurrencyMappings, { isNonEuroSepaCountry, isUKCountry } from '@/constants/countryCurrencyMapping'
1918
import { formatUnits } from 'viem'
2019
import PeanutLoading from '@/components/Global/PeanutLoading'
2120
import EmptyState from '@/components/Global/EmptyStates/EmptyState'
22-
import { UserDetailsForm, type UserDetailsFormData } from '@/components/AddMoney/UserDetailsForm'
23-
import { updateUserById } from '@/app/actions/users'
2421
import AddMoneyBankDetails from '@/components/AddMoney/components/AddMoneyBankDetails'
2522
import { getCurrencyConfig, getCurrencySymbol, getMinimumAmount } from '@/utils/bridge.utils'
2623
import { OnrampConfirmationModal } from '@/components/AddMoney/components/OnrampConfirmationModal'
27-
import { InitiateBridgeKYCModal } from '@/components/Kyc/InitiateBridgeKYCModal'
2824
import InfoCard from '@/components/Global/InfoCard'
2925
import { useQueryStates, parseAsString, parseAsStringEnum } from 'nuqs'
3026
import { useLimitsValidation } from '@/features/limits/hooks/useLimitsValidation'
3127
import LimitsWarningCard from '@/features/limits/components/LimitsWarningCard'
3228
import { getLimitsWarningCardProps } from '@/features/limits/utils'
3329
import { useExchangeRate } from '@/hooks/useExchangeRate'
30+
import { useMultiPhaseKycFlow } from '@/hooks/useMultiPhaseKycFlow'
31+
import { SumsubKycModals } from '@/components/Kyc/SumsubKycModals'
32+
import { InitiateKycModal } from '@/components/Kyc/InitiateKycModal'
3433
import posthog from 'posthog-js'
3534
import { ANALYTICS_EVENTS } from '@/constants/analytics.consts'
3635

3736
// Step type for URL state
38-
type BridgeBankStep = 'inputAmount' | 'kyc' | 'collectUserDetails' | 'showDetails'
37+
type BridgeBankStep = 'inputAmount' | 'showDetails'
3938

4039
export default function OnrampBankPage() {
4140
const router = useRouter()
@@ -45,7 +44,7 @@ export default function OnrampBankPage() {
4544
// Example: /add-money/mexico/bank?step=inputAmount&amount=500
4645
const [urlState, setUrlState] = useQueryStates(
4746
{
48-
step: parseAsStringEnum<BridgeBankStep>(['inputAmount', 'kyc', 'collectUserDetails', 'showDetails']),
47+
step: parseAsStringEnum<BridgeBankStep>(['inputAmount', 'showDetails']),
4948
amount: parseAsString,
5049
},
5150
{ history: 'push' }
@@ -56,20 +55,23 @@ export default function OnrampBankPage() {
5655

5756
// Local UI state (not URL-appropriate - transient)
5857
const [showWarningModal, setShowWarningModal] = useState<boolean>(false)
58+
const [showKycModal, setShowKycModal] = useState<boolean>(false)
5959
const [isRiskAccepted, setIsRiskAccepted] = useState<boolean>(false)
60-
const [isKycModalOpen, setIsKycModalOpen] = useState(false)
61-
const [liveKycStatus, setLiveKycStatus] = useState<BridgeKycStatus | undefined>(undefined)
62-
const [isUpdatingUser, setIsUpdatingUser] = useState(false)
63-
const [userUpdateError, setUserUpdateError] = useState<string | null>(null)
64-
const [isUserDetailsFormValid, setIsUserDetailsFormValid] = useState(false)
65-
6660
const { setError, error, setOnrampData, onrampData } = useOnrampFlow()
67-
const formRef = useRef<{ handleSubmit: () => void }>(null)
6861

6962
const { balance } = useWallet()
7063
const { user, fetchUser } = useAuth()
7164
const { createOnramp, isLoading: isCreatingOnramp, error: onrampError } = useCreateOnramp()
7265

66+
// inline sumsub kyc flow for bridge bank onramp
67+
// regionIntent is NOT passed here to avoid creating a backend record on mount.
68+
// intent is passed at call time: handleInitiateKyc('STANDARD')
69+
const sumsubFlow = useMultiPhaseKycFlow({
70+
onKycSuccess: () => {
71+
setUrlState({ step: 'inputAmount' })
72+
},
73+
})
74+
7375
const selectedCountryPath = params.country as string
7476

7577
const selectedCountry = useMemo(() => {
@@ -89,19 +91,7 @@ export default function OnrampBankPage() {
8991
// uk-specific check
9092
const isUK = isUKCountry(selectedCountryPath)
9193

92-
useWebSocket({
93-
username: user?.user.username ?? undefined,
94-
autoConnect: !!user?.user.username,
95-
onKycStatusUpdate: (newStatus) => {
96-
setLiveKycStatus(newStatus as BridgeKycStatus)
97-
},
98-
})
99-
100-
useEffect(() => {
101-
if (user?.user.bridgeKycStatus) {
102-
setLiveKycStatus(user.user.bridgeKycStatus as BridgeKycStatus)
103-
}
104-
}, [user?.user.bridgeKycStatus])
94+
const { isUserKycApproved } = useKycStatus()
10595

10696
useEffect(() => {
10797
fetchUser()
@@ -152,30 +142,12 @@ export default function OnrampBankPage() {
152142
currency: 'USD',
153143
})
154144

155-
// Determine initial step based on KYC status (only when URL has no step)
145+
// Default to inputAmount step when no step in URL
156146
useEffect(() => {
157-
// If URL already has a step, respect it (allows deep linking)
158147
if (urlState.step) return
159-
160-
// Wait for user to be fetched before determining initial step
161148
if (user === null) return
162-
163-
const currentKycStatus = liveKycStatus || user?.user.bridgeKycStatus
164-
const isUserKycVerified = currentKycStatus === 'approved'
165-
166-
if (!isUserKycVerified) {
167-
setUrlState({ step: 'collectUserDetails' })
168-
} else {
169-
setUrlState({ step: 'inputAmount' })
170-
}
171-
}, [liveKycStatus, user, urlState.step, setUrlState])
172-
173-
// Handle KYC completion
174-
useEffect(() => {
175-
if (urlState.step === 'kyc' && liveKycStatus === 'approved') {
176-
setUrlState({ step: 'inputAmount' })
177-
}
178-
}, [liveKycStatus, urlState.step, setUrlState])
149+
setUrlState({ step: 'inputAmount' })
150+
}, [user, urlState.step, setUrlState])
179151

180152
const validateAmount = useCallback(
181153
(amountStr: string): boolean => {
@@ -217,14 +189,19 @@ export default function OnrampBankPage() {
217189
}, [rawTokenAmount, validateAmount, setError])
218190

219191
const handleAmountContinue = () => {
220-
if (validateAmount(rawTokenAmount)) {
221-
posthog.capture(ANALYTICS_EVENTS.DEPOSIT_AMOUNT_ENTERED, {
222-
amount_usd: usdEquivalent,
223-
method_type: 'bank',
224-
country: selectedCountryPath,
225-
})
226-
setShowWarningModal(true)
192+
if (!validateAmount(rawTokenAmount)) return
193+
194+
if (!isUserKycApproved) {
195+
setShowKycModal(true)
196+
return
227197
}
198+
199+
posthog.capture(ANALYTICS_EVENTS.DEPOSIT_AMOUNT_ENTERED, {
200+
amount_usd: usdEquivalent,
201+
method_type: 'bank',
202+
country: selectedCountryPath,
203+
})
204+
setShowWarningModal(true)
228205
}
229206

230207
const handleWarningConfirm = async () => {
@@ -278,39 +255,6 @@ export default function OnrampBankPage() {
278255
setIsRiskAccepted(false)
279256
}
280257

281-
const handleKycSuccess = () => {
282-
setIsKycModalOpen(false)
283-
setUrlState({ step: 'inputAmount' })
284-
}
285-
286-
const handleKycModalClose = () => {
287-
setIsKycModalOpen(false)
288-
}
289-
290-
const handleUserDetailsSubmit = async (data: UserDetailsFormData) => {
291-
setIsUpdatingUser(true)
292-
setUserUpdateError(null)
293-
try {
294-
if (!user?.user.userId) throw new Error('User not found')
295-
const result = await updateUserById({
296-
userId: user.user.userId,
297-
fullName: data.fullName,
298-
email: data.email,
299-
})
300-
if (result.error) {
301-
throw new Error(result.error)
302-
}
303-
await fetchUser()
304-
setUrlState({ step: 'kyc' })
305-
} catch (error: any) {
306-
setUserUpdateError(error.message)
307-
return { error: error.message }
308-
} finally {
309-
setIsUpdatingUser(false)
310-
}
311-
return {}
312-
}
313-
314258
const handleBack = () => {
315259
if (selectedCountry) {
316260
router.push(`/add-money/${selectedCountry.path}`)
@@ -319,20 +263,6 @@ export default function OnrampBankPage() {
319263
}
320264
}
321265

322-
const initialUserDetails: Partial<UserDetailsFormData> = useMemo(
323-
() => ({
324-
fullName: user?.user.fullName ?? '',
325-
email: user?.user.email ?? '',
326-
}),
327-
[user?.user.fullName, user?.user.email]
328-
)
329-
330-
useEffect(() => {
331-
if (urlState.step === 'kyc') {
332-
setIsKycModalOpen(true)
333-
}
334-
}, [urlState.step])
335-
336266
// Redirect to inputAmount if showDetails is accessed without required data (deep link / back navigation)
337267
useEffect(() => {
338268
if (urlState.step === 'showDetails' && !onrampData?.transferId) {
@@ -359,49 +289,6 @@ export default function OnrampBankPage() {
359289
return <PeanutLoading />
360290
}
361291

362-
if (urlState.step === 'collectUserDetails') {
363-
return (
364-
<div className="flex flex-col justify-start space-y-8">
365-
<NavHeader onPrev={handleBack} title="Identity Verification" />
366-
<div className="flex flex-grow flex-col justify-center space-y-4">
367-
<h3 className="text-sm font-bold">Verify your details</h3>
368-
<UserDetailsForm
369-
ref={formRef}
370-
onSubmit={handleUserDetailsSubmit}
371-
isSubmitting={isUpdatingUser}
372-
onValidChange={setIsUserDetailsFormValid}
373-
initialData={initialUserDetails}
374-
/>
375-
<Button
376-
onClick={() => formRef.current?.handleSubmit()}
377-
loading={isUpdatingUser}
378-
variant="purple"
379-
shadowSize="4"
380-
className="w-full"
381-
disabled={!isUserDetailsFormValid || isUpdatingUser}
382-
>
383-
Continue
384-
</Button>
385-
{userUpdateError && <ErrorAlert description={userUpdateError} />}
386-
</div>
387-
</div>
388-
)
389-
}
390-
391-
if (urlState.step === 'kyc') {
392-
return (
393-
<div className="flex flex-col justify-start space-y-8">
394-
<InitiateBridgeKYCModal
395-
isOpen={isKycModalOpen}
396-
onClose={handleKycModalClose}
397-
onKycSuccess={handleKycSuccess}
398-
onManualClose={() => router.push(`/add-money/${selectedCountry.path}`)}
399-
flow="add"
400-
/>
401-
</div>
402-
)
403-
}
404-
405292
if (urlState.step === 'showDetails') {
406293
// Show loading while useEffect redirects if data is missing
407294
if (!onrampData?.transferId) {
@@ -494,6 +381,18 @@ export default function OnrampBankPage() {
494381
amount={rawTokenAmount}
495382
currency={getCurrencySymbol(getCurrencyConfig(selectedCountry.id, 'onramp').currency)}
496383
/>
384+
385+
<InitiateKycModal
386+
visible={showKycModal}
387+
onClose={() => setShowKycModal(false)}
388+
onVerify={async () => {
389+
await sumsubFlow.handleInitiateKyc('STANDARD')
390+
setShowKycModal(false)
391+
}}
392+
isLoading={sumsubFlow.isLoading}
393+
/>
394+
395+
<SumsubKycModals flow={sumsubFlow} autoStartSdk />
497396
</div>
498397
)
499398
}

0 commit comments

Comments
 (0)