From 469d2fd2bca6f8adc4ce7615cc0e6c63da20fcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Thu, 6 Nov 2025 17:21:48 -0300 Subject: [PATCH 1/9] chore: remove unused routes All these routes are not used and are surface for attack --- .../api/peanut/get-attachment-info/route.ts | 5 + .../peanut/submit-claim-link/confirm/route.ts | 155 ------------------ .../peanut/submit-claim-link/init/route.ts | 83 ---------- .../peanut/submit-direct-transfer/route.ts | 38 ----- src/components/Create/useCreateLink.tsx | 105 +----------- 5 files changed, 7 insertions(+), 379 deletions(-) delete mode 100644 src/app/api/peanut/submit-claim-link/confirm/route.ts delete mode 100644 src/app/api/peanut/submit-claim-link/init/route.ts delete mode 100644 src/app/api/peanut/submit-direct-transfer/route.ts diff --git a/src/app/api/peanut/get-attachment-info/route.ts b/src/app/api/peanut/get-attachment-info/route.ts index 63a50751f..1c0060158 100644 --- a/src/app/api/peanut/get-attachment-info/route.ts +++ b/src/app/api/peanut/get-attachment-info/route.ts @@ -5,6 +5,10 @@ import * as consts from '@/constants' import { fetchWithSentry } from '@/utils' export async function POST(request: NextRequest) { + //TODO: enable if we have attachments again, using /send-link instead of + //get-link-details + return new NextResponse(null, { status: 405 }) + /* try { const { link } = await request.json() const params = getRawParamsFromLink(link) @@ -43,4 +47,5 @@ export async function POST(request: NextRequest) { console.error('Failed to get attachment:', error) return new NextResponse('Internal Server Error', { status: 500 }) } + */ } diff --git a/src/app/api/peanut/submit-claim-link/confirm/route.ts b/src/app/api/peanut/submit-claim-link/confirm/route.ts deleted file mode 100644 index 2876471b6..000000000 --- a/src/app/api/peanut/submit-claim-link/confirm/route.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as consts from '@/constants' -import { generateKeysFromString } from '@squirrel-labs/peanut-sdk' -import type { NextRequest } from 'next/server' -import { NextResponse } from 'next/server' -import { fetchWithSentry } from '@/utils' - -export async function POST(request: NextRequest) { - try { - const { link, password, txHash, chainId, senderAddress, amountUsd, transaction } = await request.json() - - // validate required fields - if (!link || !password || !txHash || !chainId || !senderAddress) { - return new NextResponse( - JSON.stringify({ - error: 'Missing required fields', - details: 'Required: link, password, txHash, chainId, senderAddress', - }), - { - status: 400, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - - // generate pubKey from password - const { address: pubKey } = generateKeysFromString(password) - if (!pubKey) { - return new NextResponse( - JSON.stringify({ - error: 'Failed to generate pubKey', - details: 'Could not generate pubKey from password', - }), - { - status: 400, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - - // check if api key is available - const apiKey = process.env.PEANUT_API_KEY - if (!apiKey) { - return new NextResponse( - JSON.stringify({ - error: 'Server configuration error', - details: 'API key not configured', - }), - { - status: 500, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - - const formattedTransaction = transaction - ? { - from: transaction.from?.toString(), - to: transaction.to?.toString(), - data: transaction.data?.toString(), - value: transaction.value?.toString(), - } - : undefined - - const requestBody = { - txHash, - chainId, - pubKey, - link, - apiKey, - amountUsd: amountUsd || 0, - userAddress: senderAddress, - signature: '', - transaction: formattedTransaction, - } - - const response = await fetchWithSentry(`${consts.PEANUT_API_URL}/submit-claim-link/complete`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': apiKey, - }, - body: JSON.stringify(requestBody), - }) - - if (!response.ok) { - const errorData = await response.json().catch(() => null) - console.error('Backend API Error:', { - status: response.status, - statusText: response.statusText, - errorData, - requestBody: { - ...requestBody, - apiKey: '[REDACTED]', - }, - }) - return new NextResponse( - JSON.stringify({ - error: 'Failed to complete claim link submission', - details: errorData?.error || errorData?.message || response.statusText, - status: response.status, - requestData: { - pubKey, - txHash, - chainId, - link: link.substring(0, 20) + '...', // only log part of the link - }, - }), - { - status: response.status, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - - const data = await response.json() - - if (data.status !== 'completed') { - console.error('Incomplete status from backend:', data) - return new NextResponse( - JSON.stringify({ - error: 'Claim link submission incomplete', - details: `Status: ${data.status}`, - status: 400, - response: data, - }), - { - status: 400, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - - return new NextResponse(JSON.stringify({ status: 'success' }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) - } catch (error) { - console.error('Failed to complete claim link submission:', error) - return new NextResponse( - JSON.stringify({ - error: 'Failed to process request', - details: error instanceof Error ? error.message : 'Unknown error', - additionalInfo: 'An unexpected error occurred while processing the claim link submission', - debug: { - errorType: error instanceof Error ? error.constructor.name : typeof error, - stack: error instanceof Error ? error.stack : undefined, - }, - }), - { - status: 500, - headers: { 'Content-Type': 'application/json' }, - } - ) - } -} diff --git a/src/app/api/peanut/submit-claim-link/init/route.ts b/src/app/api/peanut/submit-claim-link/init/route.ts deleted file mode 100644 index 72159f355..000000000 --- a/src/app/api/peanut/submit-claim-link/init/route.ts +++ /dev/null @@ -1,83 +0,0 @@ -import * as consts from '@/constants' -import { generateKeysFromString } from '@squirrel-labs/peanut-sdk' -import type { NextRequest } from 'next/server' -import { NextResponse } from 'next/server' -import { fetchWithSentry } from '@/utils' - -export async function POST(request: NextRequest) { - try { - const formData = await request.formData() - const password = formData.get('password') as string - const senderAddress = formData.get('senderAddress') as string - const attachmentOptions = JSON.parse(formData.get('attachmentOptions') as string) - - if (!password || !senderAddress) { - return new NextResponse( - JSON.stringify({ - error: 'Missing required fields: password and/or senderAddress', - }), - { - status: 400, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - const { address: pubKey } = generateKeysFromString(password) - - const apiFormData = new FormData() - apiFormData.append('pubKey', pubKey) - apiFormData.append('apiKey', process.env.PEANUT_API_KEY ?? '') - apiFormData.append('senderAddress', senderAddress) - apiFormData.append('reference', attachmentOptions.message ?? '') - apiFormData.append('link', formData.get('link') as string) - apiFormData.append('signature', '') - const attachmentFile = formData.get('attachment') as File - if (attachmentFile) { - apiFormData.append('attachment', attachmentFile) - apiFormData.append('mimetype', attachmentFile.type) - apiFormData.append('filename', attachmentFile.name) - } - - const response = await fetchWithSentry(`${consts.PEANUT_API_URL}/submit-claim-link/init`, { - method: 'POST', - headers: { - 'api-key': process.env.PEANUT_API_KEY!, - }, - body: apiFormData, - }) - - if (!response.ok) { - const errorData = await response.json().catch(() => null) - console.error('API Error:', errorData) - return new NextResponse( - JSON.stringify({ - error: 'Failed to initialize claim link', - details: errorData?.message || response.statusText, - status: response.status, - }), - { - status: response.status, - headers: { 'Content-Type': 'application/json' }, - } - ) - } - - const data = await response.json() - return new NextResponse(JSON.stringify({ fileUrl: data.linkEntry.file_url }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) - } catch (error) { - console.error('Failed to publish file (init):', error) - return new NextResponse( - JSON.stringify({ - error: 'Failed to process request', - details: error instanceof Error ? error.message : 'Unknown error', - }), - { - status: 500, - headers: { 'Content-Type': 'application/json' }, - } - ) - } -} diff --git a/src/app/api/peanut/submit-direct-transfer/route.ts b/src/app/api/peanut/submit-direct-transfer/route.ts deleted file mode 100644 index 533a9a398..000000000 --- a/src/app/api/peanut/submit-direct-transfer/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { NextRequest } from 'next/server' -import { NextResponse } from 'next/server' -import * as consts from '@/constants' -import { fetchWithSentry } from '@/utils' - -export async function POST(request: NextRequest) { - try { - const { txHash, chainId, senderAddress, amountUsd, transaction } = await request.json() - - const response = await fetchWithSentry(`${consts.PEANUT_API_URL}/submit-direct-transfer`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - apiKey: process.env.PEANUT_API_KEY, - txHash: txHash, - chainId: chainId, - userAddress: senderAddress, - amountUsd: amountUsd, - transaction: transaction, - }), - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - const data = await response.json() - if (!data) { - throw new Error(`HTTP error! status: data undefined`) - } - - return new NextResponse(null, { status: 200 }) - } catch (error) { - console.error('Failed to push points:', error) - return new NextResponse('Internal Server Error', { status: 500 }) - } -} diff --git a/src/components/Create/useCreateLink.tsx b/src/components/Create/useCreateLink.tsx index fc73e25f6..59e9c62fd 100644 --- a/src/components/Create/useCreateLink.tsx +++ b/src/components/Create/useCreateLink.tsx @@ -1,8 +1,8 @@ 'use client' import { getLinkFromTx, getNextDepositIndex } from '@/app/actions/claimLinks' -import { PEANUT_API_URL, PEANUT_WALLET_CHAIN, PEANUT_WALLET_TOKEN, next_proxy_url } from '@/constants' +import { PEANUT_WALLET_CHAIN, PEANUT_WALLET_TOKEN, next_proxy_url } from '@/constants' import { loadingStateContext, tokenSelectorContext } from '@/context' -import { fetchWithSentry, isNativeCurrency, saveToLocalStorage } from '@/utils' +import { isNativeCurrency, saveToLocalStorage } from '@/utils' import peanut, { generateKeysFromString, getLatestContractVersion, @@ -17,7 +17,6 @@ import { useSignTypedData } from 'wagmi' import { useZeroDev } from '@/hooks/useZeroDev' import { useWallet } from '@/hooks/wallet/useWallet' -import { captureException } from '@sentry/nextjs' export const useCreateLink = () => { const { setLoadingState } = useContext(loadingStateContext) @@ -93,104 +92,6 @@ export const useCreateLink = () => { [address] ) - const submitClaimLinkInit = async ({ - attachmentOptions, - password, - senderAddress, - }: { - password: string - attachmentOptions: { - message?: string - attachmentFile?: File - } - senderAddress: string - }) => { - try { - const formData = new FormData() - formData.append('password', password) - formData.append('attachmentOptions', JSON.stringify(attachmentOptions)) - formData.append('senderAddress', senderAddress) - - if (attachmentOptions.attachmentFile) { - formData.append('attachment', attachmentOptions.attachmentFile) - } - - const response = await fetchWithSentry('/api/peanut/submit-claim-link/init', { - method: 'POST', - body: formData, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - const data = await response.json() - - return data - } catch (error) { - console.error('Failed to publish file (init):', error) - return '' - } - } - const submitClaimLinkConfirm = async ({ - link, - password, - txHash, - chainId, - senderAddress, - amountUsd, - transaction, - }: { - link: string - password: string - txHash: string - chainId: string - senderAddress: string - amountUsd: number - transaction?: peanutInterfaces.IPeanutUnsignedTransaction - }) => { - try { - const { address: pubKey } = generateKeysFromString(password) - if (!pubKey) { - throw new Error('Failed to generate pubKey from password') - } - - const formattedTransaction = transaction - ? { - from: transaction.from?.toString(), - to: transaction.to?.toString(), - data: transaction.data?.toString(), - value: transaction.value?.toString(), - } - : undefined - - const response = await fetchWithSentry('/api/peanut/submit-claim-link/confirm', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - link, - password, - txHash, - chainId, - senderAddress, - amountUsd, - pubKey, - signature: '', - transaction: formattedTransaction, - }), - }) - - if (!response.ok) { - const errorData = await response.json() - throw new Error(errorData.error || `HTTP error! status: ${response.status}`) - } - } catch (error) { - console.error('Failed to publish file (complete):', error) - throw error - } - } - const prepareDirectSendTx = ({ recipient, tokenValue, @@ -389,8 +290,6 @@ export const useCreateLink = () => { makeDepositGasless, prepareDepositTxs, getLinkFromHash, - submitClaimLinkInit, - submitClaimLinkConfirm, prepareDirectSendTx, createLink, } From d1fcf587bbe84e668d579ad7c95b192cd198f716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Fri, 7 Nov 2025 00:00:05 -0300 Subject: [PATCH 2/9] chore: add error message for pix --- src/app/(mobile-ui)/qr-pay/page.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index a4506cce9..ecf161ec3 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -433,14 +433,20 @@ export default function QRPayPage() { }) .catch((error) => { if (error.message.includes("provider can't decode it")) { - setWaitingForMerchantAmount(true) + if (EQrType.PIX === qrType) { + setErrorInitiatingPayment( + 'We are currently experiencing issues with PIX payments due to an external provider. We are working to fix it as soon as possible' + ) + } else { + setWaitingForMerchantAmount(true) + } } else { setErrorInitiatingPayment(error.message) setWaitingForMerchantAmount(false) } }) .finally(() => setLoadingState('Idle')) - }, [paymentLock, qrCode, setLoadingState, paymentProcessor, shouldRetry]) + }, [paymentLock, qrCode, setLoadingState, paymentProcessor, shouldRetry, qrType]) const merchantName = useMemo(() => { if (paymentProcessor === 'SIMPLEFI') { @@ -836,14 +842,14 @@ export default function QRPayPage() { if (!!errorInitiatingPayment) { return (
- -
-

Unable to get QR details

-

- {errorInitiatingPayment || 'An error occurred while getting the QR details.'} -

+ +
+
-
+

+ {' '} + {errorInitiatingPayment || 'An error occurred while getting the QR details.'} +

-

High Balance Warning

+

High Balance

+

You're rich! Congrats on having a high balance.

- Peanut is completely self-custodial and you need your biometric passkey to access your - account. + With Peanut, you're the only one who can access your funds. No bank, no company, no agency — + not even our support team — can ever access them.

- No support team ever has access to your account and cannot recover it.{' '} - {platformInfo && ( - <> - Learn more about how to secure your passkey on{' '} - - {platformInfo.name} - - . - - )} + Your biometric passkey is your key to full control and security. If it's lost, it can't be + recovered because only you ever have access.

+ + {platformInfo && ( + <> + Learn how to keep your passkey secure on{' '} + + {platformInfo.name} + + . + + )}
- +
) diff --git a/src/components/Slider/index.tsx b/src/components/Slider/index.tsx index 5aa386db6..d2ab59ca8 100644 --- a/src/components/Slider/index.tsx +++ b/src/components/Slider/index.tsx @@ -14,10 +14,11 @@ export interface SliderProps onValueChange?: (value: boolean) => void defaultValue?: boolean onAccepted?: () => void + title?: string } const Slider = React.forwardRef, SliderProps>( - ({ className, value, onValueChange, defaultValue, onAccepted, ...props }, ref) => { + ({ className, value, onValueChange, defaultValue, onAccepted, title, ...props }, ref) => { const isControlled = value !== undefined const [uncontrolledState, setUncontrolledState] = React.useState(defaultValue ?? false) const currentValue = isControlled ? value : uncontrolledState @@ -66,7 +67,7 @@ const Slider = React.forwardRef, S
- Slide to Proceed + {title ? title : 'Slide to Proceed'}
Date: Fri, 7 Nov 2025 19:15:58 +0530 Subject: [PATCH 4/9] fix: Request pots redirect --- src/hooks/useLogin.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useLogin.tsx b/src/hooks/useLogin.tsx index 84b65d568..ce2991df6 100644 --- a/src/hooks/useLogin.tsx +++ b/src/hooks/useLogin.tsx @@ -33,11 +33,11 @@ export const useLogin = () => { const redirect_uri = searchParams.get('redirect_uri') if (redirect_uri) { const validRedirectUrl = getValidRedirectUrl(redirect_uri, '/home') - router.push(validRedirectUrl) + window.location.assign(validRedirectUrl) } else if (localStorageRedirect) { clearRedirectUrl() const validRedirectUrl = getValidRedirectUrl(String(localStorageRedirect), '/home') - router.push(validRedirectUrl) + window.location.assign(validRedirectUrl) } else { router.push('/home') } From 9e619cbcc1fcd4b969883cda77b52d59040c19b2 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Fri, 7 Nov 2025 21:18:51 +0530 Subject: [PATCH 5/9] Disable Add money modal --- src/app/(mobile-ui)/home/page.tsx | 69 ++----------------------------- 1 file changed, 3 insertions(+), 66 deletions(-) diff --git a/src/app/(mobile-ui)/home/page.tsx b/src/app/(mobile-ui)/home/page.tsx index 5cca971d8..bfa89bffc 100644 --- a/src/app/(mobile-ui)/home/page.tsx +++ b/src/app/(mobile-ui)/home/page.tsx @@ -61,7 +61,6 @@ export default function Home() { const username = user?.user.username const [showIOSPWAInstallModal, setShowIOSPWAInstallModal] = useState(false) - const [showAddMoneyPromptModal, setShowAddMoneyPromptModal] = useState(false) const [showBalanceWarningModal, setShowBalanceWarningModal] = useState(false) // const [showReferralCampaignModal, setShowReferralCampaignModal] = useState(false) const [isPostSignupActionModalVisible, setIsPostSignupActionModalVisible] = useState(false) @@ -155,75 +154,12 @@ export default function Home() { balanceInUsd > BALANCE_WARNING_THRESHOLD && !hasSeenBalanceWarning && !showIOSPWAInstallModal && - !showAddMoneyPromptModal && !isPostSignupActionModalVisible ) { setShowBalanceWarningModal(true) } } - }, [ - balance, - isFetchingBalance, - showIOSPWAInstallModal, - showAddMoneyPromptModal, - isPostSignupActionModalVisible, - user, - ]) - - // effect for showing add money prompt modal - useEffect(() => { - if (typeof window === 'undefined' || isFetchingBalance || !user || !address) return - - // Don't show modal if balance is still loading (undefined) - if (balance === undefined) return - - const hasSeenAddMoneyPromptThisSession = sessionStorage.getItem('hasSeenAddMoneyPromptThisSession') - const showNoMoreJailModal = sessionStorage.getItem('showNoMoreJailModal') - - // determine if we should show the add money modal based on all conditions - // show if: - // 1. balance is zero. - // 2. user hasn't seen this prompt in the current session. - // 3. setup notifications modal is not visible (priority: setup modal > add money prompt) - // 4. the iOS PWA install modal is not currently active. - // 5. the balance warning modal is not currently active. - // 6. no other post-signup modal is active - const shouldShow = - balance === 0n && - !hasSeenAddMoneyPromptThisSession && - !showPermissionModal && - !showIOSPWAInstallModal && - !showBalanceWarningModal && - !isPostSignupActionModalVisible && - showNoMoreJailModal !== 'true' && - !user?.showEarlyUserModal // Give Early User and No more jail modal precedence, showing two modals together isn't ideal and it messes up their functionality - - if (shouldShow && !showAddMoneyPromptModal) { - // Only set state, don't set sessionStorage here to avoid race conditions - setShowAddMoneyPromptModal(true) - } else if (showAddMoneyPromptModal && showPermissionModal) { - // priority enforcement: hide add money modal if notification modal appears - // this handles race conditions where both modals try to show simultaneously - setShowAddMoneyPromptModal(false) - } - }, [ - balance, - isFetchingBalance, - showPermissionModal, - showIOSPWAInstallModal, - showBalanceWarningModal, - isPostSignupActionModalVisible, - showAddMoneyPromptModal, - user, - address, - ]) - - // Set sessionStorage flag when modal becomes visible to prevent showing again - useEffect(() => { - if (showAddMoneyPromptModal) { - sessionStorage.setItem('hasSeenAddMoneyPromptThisSession', 'true') - } - }, [showAddMoneyPromptModal]) + }, [balance, isFetchingBalance, showIOSPWAInstallModal, isPostSignupActionModalVisible, user]) if (isLoading) { return @@ -283,7 +219,8 @@ export default function Home() { setShowIOSPWAInstallModal(false)} /> {/* Add Money Prompt Modal */} - setShowAddMoneyPromptModal(false)} /> + {/* TODO @dev Disabling this, re-enable after properly fixing */} + {/* setShowAddMoneyPromptModal(false)} /> */} From 2258296de1b9b74145728ea2d5a1c41fdfa2d195 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Fri, 7 Nov 2025 21:19:31 +0530 Subject: [PATCH 6/9] Add QR payment CTA --- src/components/Global/DirectSendQR/index.tsx | 3 +- src/components/Global/Icons/qr-code.tsx | 2 +- .../Home/HomeCarouselCTA/CarouselCTA.tsx | 4 +- src/components/Home/HomeCarouselCTA/index.tsx | 1 + src/context/QrCodeContext.tsx | 23 ++++++++++ src/context/contextProvider.tsx | 5 ++- src/hooks/useHomeCarouselCTAs.tsx | 45 +++++++++++-------- 7 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 src/context/QrCodeContext.tsx diff --git a/src/components/Global/DirectSendQR/index.tsx b/src/components/Global/DirectSendQR/index.tsx index 2cffa0c34..7e2ea18ff 100644 --- a/src/components/Global/DirectSendQR/index.tsx +++ b/src/components/Global/DirectSendQR/index.tsx @@ -18,6 +18,7 @@ import { twMerge } from 'tailwind-merge' import ActionModal from '../ActionModal' import { Icon, type IconName } from '../Icons/Icon' import { EQrType, NAME_BY_QR_TYPE, parseEip681, recognizeQr } from './utils' +import { useQrCodeContext } from '@/context/QrCodeContext' const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL! @@ -180,7 +181,6 @@ export default function DirectSendQr({ icon?: IconName disabled?: boolean }) { - const [isQRScannerOpen, setIsQRScannerOpen] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [showPermissionModal, setShowPermissionModal] = useState(false) const [qrType, setQrType] = useState(undefined) @@ -196,6 +196,7 @@ export default function DirectSendQr({ if (!user?.user.username) return '' return `${BASE_URL}/pay/${user.user.username}` }, [user?.user.username]) + const { isQRScannerOpen, setIsQRScannerOpen } = useQrCodeContext() const startScanner = () => { setIsQRScannerOpen(true) diff --git a/src/components/Global/Icons/qr-code.tsx b/src/components/Global/Icons/qr-code.tsx index 82b98eceb..8fb1c5553 100644 --- a/src/components/Global/Icons/qr-code.tsx +++ b/src/components/Global/Icons/qr-code.tsx @@ -1,7 +1,7 @@ import { type FC, type SVGProps } from 'react' export const QrCodeIcon: FC> = (props) => ( - + { const [showPermissionDeniedModal, setShowPermissionDeniedModal] = useState(false) @@ -95,7 +97,7 @@ const CarouselCTA = ({ )} > {/* Show icon only if logo isn't provided. Logo takes precedence over icon. */} - {!logo && } + {!logo && } {logo && ( )} diff --git a/src/components/Home/HomeCarouselCTA/index.tsx b/src/components/Home/HomeCarouselCTA/index.tsx index 779b4ccff..2a4e67803 100644 --- a/src/components/Home/HomeCarouselCTA/index.tsx +++ b/src/components/Home/HomeCarouselCTA/index.tsx @@ -32,6 +32,7 @@ const HomeCarouselCTA = () => { iconContainerClassName={cta.iconContainerClassName} isPermissionDenied={cta.isPermissionDenied} secondaryIcon={cta.secondaryIcon} + iconSize={16} /> ))} diff --git a/src/context/QrCodeContext.tsx b/src/context/QrCodeContext.tsx new file mode 100644 index 000000000..53c740f71 --- /dev/null +++ b/src/context/QrCodeContext.tsx @@ -0,0 +1,23 @@ +'use client' + +import { createContext, useContext, useState, type ReactNode } from 'react' + +interface QrCodeContextType { + isQRScannerOpen: boolean + setIsQRScannerOpen: (isOpen: boolean) => void +} + +const QrCodeContext = createContext(undefined) + +export function QrCodeProvider({ children }: { children: ReactNode }) { + const [isQRScannerOpen, setIsQRScannerOpen] = useState(false) + return {children} +} + +export function useQrCodeContext() { + const context = useContext(QrCodeContext) + if (context === undefined) { + throw new Error('useQrCodeContext must be used within a QrCodeProvider') + } + return context +} diff --git a/src/context/contextProvider.tsx b/src/context/contextProvider.tsx index 01dcb29c7..d285c51e7 100644 --- a/src/context/contextProvider.tsx +++ b/src/context/contextProvider.tsx @@ -9,6 +9,7 @@ import { WithdrawFlowContextProvider } from './WithdrawFlowContext' import { ClaimBankFlowContextProvider } from './ClaimBankFlowContext' import { RequestFulfilmentFlowContextProvider } from './RequestFulfillmentFlowContext' import { SupportModalProvider } from './SupportModalContext' +import { QrCodeProvider } from './QrCodeContext' export const ContextProvider = ({ children }: { children: React.ReactNode }) => { return ( @@ -22,7 +23,9 @@ export const ContextProvider = ({ children }: { children: React.ReactNode }) => - {children} + + {children} + diff --git a/src/hooks/useHomeCarouselCTAs.tsx b/src/hooks/useHomeCarouselCTAs.tsx index 6103e5653..bdea5d4e7 100644 --- a/src/hooks/useHomeCarouselCTAs.tsx +++ b/src/hooks/useHomeCarouselCTAs.tsx @@ -7,7 +7,7 @@ import { useNotifications } from './useNotifications' import { useRouter } from 'next/navigation' import useKycStatus from './useKycStatus' import type { StaticImageData } from 'next/image' -import { PIX } from '@/assets' +import { useQrCodeContext } from '@/context/QrCodeContext' export type CarouselCTA = { id: string @@ -21,6 +21,7 @@ export type CarouselCTA = { isPermissionDenied?: boolean iconContainerClassName?: string secondaryIcon?: StaticImageData | string + iconSize?: number } export const useHomeCarouselCTAs = () => { @@ -29,27 +30,35 @@ export const useHomeCarouselCTAs = () => { const { showReminderBanner, requestPermission, snoozeReminderBanner, afterPermissionAttempt, isPermissionDenied } = useNotifications() const router = useRouter() - const { isUserKycApproved, isUserBridgeKycUnderReview } = useKycStatus() + const { isUserKycApproved, isUserBridgeKycUnderReview, isUserMantecaKycApproved } = useKycStatus() + + const { setIsQRScannerOpen } = useQrCodeContext() const generateCarouselCTAs = useCallback(() => { const _carouselCTAs: CarouselCTA[] = [] - _carouselCTAs.push({ - id: 'merchant-map-pix', - title: 'Up to 10% cashback for Tier 2 users with PIX Payments', - description: 'Click to explore participating merchants. Pay with PIX QR, save instantly, earn points.', - iconContainerClassName: 'bg-secondary-1', - icon: 'shield', - onClick: () => { - window.open( - 'https://peanutprotocol.notion.site/Peanut-Foodie-Guide-29a83811757980e79896f2a610d6591a', - '_blank', - 'noopener,noreferrer' - ) - }, - logo: PIX, - secondaryIcon: 'https://flagcdn.com/w320/br.png', - }) + // Show QR code payment prompt if user's Bridge or Manteca KYC is approved. + if (isUserKycApproved || isUserMantecaKycApproved) { + _carouselCTAs.push({ + id: 'qr-payment', + title: ( +

+ Pay with QR code payments +

+ ), + description: ( +

+ Get the best exchange rate, pay like a local and earn points. +

+ ), + iconContainerClassName: 'bg-secondary-1', + icon: 'qr-code', + onClick: () => { + setIsQRScannerOpen(true) + }, + iconSize: 16, + }) + } // add notification prompt as first item if it should be shown if (showReminderBanner) { From 7602eb2dee99d937f81b88ce91fefbdbeb20e74d Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Fri, 7 Nov 2025 21:23:15 +0530 Subject: [PATCH 7/9] Update request pot button text --- src/components/Payment/PaymentForm/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Payment/PaymentForm/index.tsx b/src/components/Payment/PaymentForm/index.tsx index fd73722cd..50105b7f7 100644 --- a/src/components/Payment/PaymentForm/index.tsx +++ b/src/components/Payment/PaymentForm/index.tsx @@ -540,7 +540,7 @@ export const PaymentForm = ({ } if (showRequestPotInitialView) { - return 'Pay' + return 'Choose payment method' } if (isActivePeanutWallet && isInsufficientBalanceError && !isExternalWalletFlow) { From 6c8ac2bd84ba74635a3a0fda279cc1b15972a383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Fri, 7 Nov 2025 16:54:47 -0300 Subject: [PATCH 8/9] fix: remove search from user search --- src/components/Home/HomeHistory.tsx | 21 +++---- .../Profile/components/PublicProfile.tsx | 2 +- src/hooks/useTransactionHistory.ts | 34 ++--------- src/hooks/useUserSearch.ts | 61 ------------------- src/services/users.ts | 6 -- 5 files changed, 15 insertions(+), 109 deletions(-) delete mode 100644 src/hooks/useUserSearch.ts diff --git a/src/components/Home/HomeHistory.tsx b/src/components/Home/HomeHistory.tsx index 73ebe745f..763852f60 100644 --- a/src/components/Home/HomeHistory.tsx +++ b/src/components/Home/HomeHistory.tsx @@ -24,20 +24,17 @@ import { PEANUT_WALLET_TOKEN_DECIMALS } from '@/constants' /** * component to display a preview of the most recent transactions on the home page. */ -const HomeHistory = ({ isPublic = false, username }: { isPublic?: boolean; username?: string }) => { +const HomeHistory = ({ username }: { username?: string }) => { const { user } = useUserStore() const isLoggedIn = !!user?.user.userId || false - // fetch the latest 5 transaction history entries - const mode = isPublic ? 'public' : 'latest' - const limit = isPublic ? 20 : 5 // Only filter when user is requesting for some different user's history - const filterMutualTxs = !isPublic && username !== user?.user.username + const filterMutualTxs = username !== user?.user.username const { data: historyData, isLoading, isError, error, - } = useTransactionHistory({ mode, limit, username, filterMutualTxs, enabled: isLoggedIn }) + } = useTransactionHistory({ mode: 'latest', limit: 5, username, filterMutualTxs, enabled: isLoggedIn }) // check if the username is the same as the current user const { fetchBalance } = useWallet() @@ -168,9 +165,9 @@ const HomeHistory = ({ isPublic = false, username }: { isPublic?: boolean; usern } } - // Add KYC status item if applicable and not on a public page - // and the user is viewing their own history - if (isViewingOwnHistory && !isPublic) { + // Add KYC status item if applicable and the user is + // viewing their own history + if (isViewingOwnHistory) { if (user?.user?.bridgeKycStatus && user.user.bridgeKycStatus !== 'not_started') { entries.push({ isKyc: true, @@ -200,7 +197,7 @@ const HomeHistory = ({ isPublic = false, username }: { isPublic?: boolean; usern }) // Limit to the most recent entries - setCombinedEntries(entries.slice(0, isPublic ? 20 : 5)) + setCombinedEntries(entries.slice(0, 5)) } processEntries() @@ -210,7 +207,7 @@ const HomeHistory = ({ isPublic = false, username }: { isPublic?: boolean; usern cancelled = true } } - }, [historyData, wsHistoryEntries, isPublic, user, isLoading, isViewingOwnHistory]) + }, [historyData, wsHistoryEntries, user, isLoading, isViewingOwnHistory]) const pendingRequests = useMemo(() => { if (!combinedEntries.length) return [] @@ -302,7 +299,7 @@ const HomeHistory = ({ isPublic = false, username }: { isPublic?: boolean; usern return (
{/* link to the full history page */} - {pendingRequests.length > 0 && !isPublic && ( + {pendingRequests.length > 0 && ( <>

Pending transactions

diff --git a/src/components/Profile/components/PublicProfile.tsx b/src/components/Profile/components/PublicProfile.tsx index e6d55ef71..6537275ea 100644 --- a/src/components/Profile/components/PublicProfile.tsx +++ b/src/components/Profile/components/PublicProfile.tsx @@ -215,7 +215,7 @@ const PublicProfile: React.FC = ({ username, isLoggedIn = fa {/* Show history to logged in users */} {isLoggedIn && (
- + {isSelfProfile && (
diff --git a/src/hooks/useTransactionHistory.ts b/src/hooks/useTransactionHistory.ts index 239ed3cd3..cbb7689e0 100644 --- a/src/hooks/useTransactionHistory.ts +++ b/src/hooks/useTransactionHistory.ts @@ -22,7 +22,7 @@ export type HistoryResponse = { // Hook options type UseTransactionHistoryOptions = { - mode?: 'infinite' | 'latest' | 'public' + mode?: 'infinite' | 'latest' limit?: number enabled?: boolean username?: string @@ -30,7 +30,7 @@ type UseTransactionHistoryOptions = { } export function useTransactionHistory(options: { - mode: 'latest' | 'public' + mode: 'latest' limit?: number enabled?: boolean username?: string @@ -55,34 +55,19 @@ export function useTransactionHistory({ username, filterMutualTxs, }: UseTransactionHistoryOptions): LatestHistoryResult | InfiniteHistoryResult { - const fetchHistory = async ({ - cursor, - limit, - isPublic = false, - }: { - cursor?: string - limit: number - isPublic?: boolean - }): Promise => { + const fetchHistory = async ({ cursor, limit }: { cursor?: string; limit: number }): Promise => { const queryParams = new URLSearchParams() if (cursor) queryParams.append('cursor', cursor) if (limit) queryParams.append('limit', limit.toString()) // append targetUsername to the query params if filterMutualTxs is true and username is provided if (filterMutualTxs && username) queryParams.append('targetUsername', username) - let url: string - if (isPublic) { - url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}` - } else { - url = `${PEANUT_API_URL}/users/history?${queryParams.toString()}` - } + const url = `${PEANUT_API_URL}/users/history?${queryParams.toString()}` const headers: Record = { 'Content-Type': 'application/json', } - if (!isPublic) { - headers['Authorization'] = `Bearer ${Cookies.get('jwt-token')}` - } + headers['Authorization'] = `Bearer ${Cookies.get('jwt-token')}` const response = await fetchWithSentry(url, { method: 'GET', headers }) if (!response.ok) { @@ -109,15 +94,6 @@ export function useTransactionHistory({ }) } - if (mode === 'public') { - return useQuery({ - queryKey: [TRANSACTIONS, 'public', username, { limit }], - queryFn: () => fetchHistory({ limit, isPublic: true }), - enabled, - staleTime: 15 * 1000, // 15 seconds - }) - } - // Infinite query mode (for main history page) return useInfiniteQuery({ queryKey: [TRANSACTIONS, 'infinite', { limit }], diff --git a/src/hooks/useUserSearch.ts b/src/hooks/useUserSearch.ts deleted file mode 100644 index c0ea2408b..000000000 --- a/src/hooks/useUserSearch.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useUserStore } from '@/redux/hooks' -import { type ApiUser, usersApi } from '@/services/users' -import { useEffect, useRef, useState } from 'react' -import { useDebounce } from './useDebounce' - -export const useUserSearch = () => { - const { user: authenticatedUser } = useUserStore() - const [searchTerm, setSearchTerm] = useState('') - const debouncedValue = useDebounce(searchTerm, 300) - const [searchResults, setSearchResults] = useState([]) - const [isSearching, setIsSearching] = useState(false) - const [error, setError] = useState('') - const currentValueRef = useRef(searchTerm) - - // Update currentValueRef when searchTerm changes - useEffect(() => { - currentValueRef.current = searchTerm - }, [searchTerm]) - - // handle API call when debounced value changes - useEffect(() => { - if (!debouncedValue) { - setSearchResults([]) - return - } - - const performSearch = async () => { - try { - setIsSearching(true) - setError('') - const response = await usersApi.search(debouncedValue) - if (currentValueRef.current === debouncedValue) { - const filteredUsers = response.users.filter( - (user) => user.userId !== authenticatedUser?.user.userId - ) - setSearchResults(filteredUsers) - } - } catch (err) { - if (err instanceof Error && err.message.includes('3 characters')) { - setSearchResults([]) - } else { - setError('Failed to search users') - } - } finally { - setIsSearching(false) - } - } - - performSearch() - }, [debouncedValue]) - - return { - searchTerm, - setSearchTerm, - searchResults, - isSearching, - error, - showMinCharError: searchTerm.length > 0 && searchTerm.length < 3, - showNoResults: searchTerm.length >= 3 && !isSearching && searchResults.length === 0, - } -} diff --git a/src/services/users.ts b/src/services/users.ts index 9d3166439..241398ea9 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -69,12 +69,6 @@ export const usersApi = { return await response.json() }, - search: async (query: string): Promise => { - if (query.length < 3) throw new Error('Search query must be at least 3 characters') - const response = await fetchWithSentry(`${PEANUT_API_URL}/users/search?q=${query}`) - return await response.json() - }, - requestByUsername: async ({ username, amount, From ea9a23d4e40f298bcd3a8f8daad2568c639c3e7e Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Sat, 8 Nov 2025 14:03:29 -0300 Subject: [PATCH 9/9] Revert "Merge branch 'peanut-wallet-dev' into peanut-wallet" This reverts commit bc3ac14fe4006f3175facf5392b2e3e08d1aaf14, reversing changes made to 6c8ac2bd84ba74635a3a0fda279cc1b15972a383. --- package.json | 4 - pnpm-lock.yaml | 248 ++-------------- src/app/(mobile-ui)/home/page.tsx | 18 +- .../[region]/[country]/page.tsx | 6 - .../identity-verification/[region]/page.tsx | 10 - .../profile/identity-verification/layout.tsx | 59 ---- .../profile/identity-verification/page.tsx | 9 +- src/app/(mobile-ui)/withdraw/crypto/page.tsx | 3 - src/app/(mobile-ui)/withdraw/manteca/page.tsx | 4 +- src/app/[...recipient]/client.tsx | 4 - src/assets/icons/europe-globe.svg | 19 -- src/assets/icons/index.ts | 4 - src/assets/icons/latam-globe.svg | 17 -- src/assets/icons/north-america-globe.svg | 33 --- src/assets/icons/rest-of-world-globe.svg | 14 - src/components/0_Bruddle/BaseInput.tsx | 28 +- src/components/0_Bruddle/BaseSelect.tsx | 103 ------- src/components/0_Bruddle/Button.tsx | 10 - src/components/0_Bruddle/index.ts | 1 - src/components/ActionListCard/index.tsx | 10 +- src/components/AddMoney/consts/index.ts | 245 ---------------- .../AddWithdraw/DynamicBankAccountForm.tsx | 81 +----- src/components/Claim/Claim.tsx | 7 +- .../Claim/Link/Onchain/Confirm.view.tsx | 2 +- .../Claim/Link/Onchain/Success.view.tsx | 24 +- src/components/Common/CountryList.tsx | 18 +- src/components/Global/DirectSendQR/index.tsx | 3 - src/components/Global/Icons/Icon.tsx | 3 - src/components/Global/Icons/globe-lock.tsx | 16 -- .../Global/TokenAmountInput/index.tsx | 24 +- .../Global/UnsupportedBrowserModal/index.tsx | 27 +- .../Global/WalletNavigation/index.tsx | 8 +- .../Home/HomeCarouselCTA/CarouselCTA.tsx | 3 - src/components/Home/HomeHistory.tsx | 4 +- .../Home/KycCompletedModal/index.tsx | 105 ------- .../StartVerificationModal.tsx | 106 ------- src/components/Invites/InvitesPage.tsx | 2 - src/components/Payment/PaymentForm/index.tsx | 3 - .../Payment/Views/Confirm.payment.view.tsx | 3 - .../Payment/Views/Status.payment.view.tsx | 18 +- .../Profile/components/CountryListSection.tsx | 79 ----- .../IdentityVerificationCountryList.tsx | 70 ----- src/components/Profile/index.tsx | 17 +- .../views/IdentityVerification.view.tsx | 146 +++++----- .../Profile/views/RegionsPage.view.tsx | 43 --- .../views/RegionsVerification.view.tsx | 81 ------ .../TransactionDetails/TransactionCard.tsx | 3 - src/constants/index.ts | 1 - src/constants/stateCodes.consts.ts | 99 ------- src/context/contextProvider.tsx | 5 +- src/context/passkeySupportContext.tsx | 16 -- src/hooks/useIdentityVerification.tsx | 272 ------------------ src/interfaces/interfaces.ts | 1 - src/utils/__mocks__/simplewebauthn-browser.ts | 53 ---- src/utils/identityVerification.tsx | 22 -- tailwind.config.js | 10 - 56 files changed, 176 insertions(+), 2048 deletions(-) delete mode 100644 src/app/(mobile-ui)/profile/identity-verification/[region]/[country]/page.tsx delete mode 100644 src/app/(mobile-ui)/profile/identity-verification/[region]/page.tsx delete mode 100644 src/app/(mobile-ui)/profile/identity-verification/layout.tsx delete mode 100644 src/assets/icons/europe-globe.svg delete mode 100644 src/assets/icons/latam-globe.svg delete mode 100644 src/assets/icons/north-america-globe.svg delete mode 100644 src/assets/icons/rest-of-world-globe.svg delete mode 100644 src/components/0_Bruddle/BaseSelect.tsx delete mode 100644 src/components/Global/Icons/globe-lock.tsx delete mode 100644 src/components/Home/KycCompletedModal/index.tsx delete mode 100644 src/components/IdentityVerification/StartVerificationModal.tsx delete mode 100644 src/components/Profile/components/CountryListSection.tsx delete mode 100644 src/components/Profile/components/IdentityVerificationCountryList.tsx delete mode 100644 src/components/Profile/views/RegionsPage.view.tsx delete mode 100644 src/components/Profile/views/RegionsVerification.view.tsx delete mode 100644 src/constants/stateCodes.consts.ts delete mode 100644 src/context/passkeySupportContext.tsx delete mode 100644 src/hooks/useIdentityVerification.tsx delete mode 100644 src/utils/__mocks__/simplewebauthn-browser.ts delete mode 100644 src/utils/identityVerification.tsx diff --git a/package.json b/package.json index 2941af4a4..ed31e3a45 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,6 @@ "@hookform/resolvers": "3.9.1", "@justaname.id/react": "0.3.180", "@justaname.id/sdk": "0.2.177", - "@radix-ui/react-accordion": "^1.2.12", - "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.5", "@reduxjs/toolkit": "^2.5.0", "@reown/appkit": "1.6.9", @@ -81,7 +79,6 @@ "siwe": "^2.3.2", "tailwind-merge": "^1.14.0", "tailwind-scrollbar": "^3.1.0", - "use-haptic": "^1.1.11", "uuid": "^10.0.0", "validator": "^13.12.0", "vaul": "^1.1.2", @@ -152,7 +149,6 @@ "^web-push$": "/src/utils/__mocks__/web-push.ts", "^next/cache$": "/src/utils/__mocks__/next-cache.ts", "^@zerodev/sdk(.*)$": "/src/utils/__mocks__/zerodev-sdk.ts", - "^@simplewebauthn/browser$": "/src/utils/__mocks__/simplewebauthn-browser.ts", "^@/(.*)$": "/src/$1" }, "setupFilesAfterEnv": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5454239d..2c4d13456 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ importers: version: 2.0.4 '@daimo/pay': specifier: ^1.16.5 - version: 1.16.5(aab9c1b916d14222248b36333fb0cd5f) + version: 1.16.5(0133d12034a313dd0ab10c88bd8ab5d6) '@dicebear/collection': specifier: ^9.2.2 version: 9.2.4(@dicebear/core@9.2.4) @@ -56,12 +56,6 @@ importers: '@justaname.id/sdk': specifier: 0.2.177 version: 0.2.177(ethers@5.7.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(siwe@2.3.2(ethers@5.7.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(viem@2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)) - '@radix-ui/react-accordion': - specifier: ^1.2.12 - version: 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-select': - specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-slider': specifier: ^1.3.5 version: 1.3.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -197,9 +191,6 @@ importers: tailwind-scrollbar: specifier: ^3.1.0 version: 3.1.0(tailwindcss@3.4.17) - use-haptic: - specifier: ^1.1.11 - version: 1.1.11 uuid: specifier: ^10.0.0 version: 10.0.0 @@ -2348,45 +2339,6 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@radix-ui/react-accordion@1.2.12': - resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collapsible@1.1.12': - resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: @@ -2484,19 +2436,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-portal@1.1.9': resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: @@ -2536,19 +2475,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.2.6': - resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-slider@1.3.6': resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} peerDependencies: @@ -2625,15 +2551,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-size@1.1.1': resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: @@ -2643,22 +2560,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@react-aria/focus@3.21.2': resolution: {integrity: sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==} peerDependencies: @@ -7478,9 +7379,6 @@ packages: '@types/react': optional: true - use-haptic@1.1.11: - resolution: {integrity: sha512-UiLReGEOPzgONQlvhoUtOWLRDzc6B3LNozPpyhbg22RzLAN6XCvD2ujiBEaYRioCNI4e/dXaUkSSEa7YEZNjLw==} - use-sidecar@1.1.3: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} @@ -8707,11 +8605,11 @@ snapshots: - typescript - utf-8-validate - '@daimo/pay@1.16.5(aab9c1b916d14222248b36333fb0cd5f)': + '@daimo/pay@1.16.5(0133d12034a313dd0ab10c88bd8ab5d6)': dependencies: '@daimo/pay-common': 1.16.5(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-react': 0.15.39(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.0(@babel/core@7.28.3)(@react-native/metro-config@0.81.0(@babel/core@7.28.3))(@types/react@18.3.23)(bufferutil@4.0.9)(react@19.1.1)(utf-8-validate@5.0.10))(react@19.1.1) + '@solana/wallet-adapter-react': 0.15.39(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@6.0.0)(react-native@0.81.0(@babel/core@7.28.3)(@react-native/metro-config@0.81.0(@babel/core@7.28.3))(@types/react@18.3.23)(bufferutil@4.0.9)(react@19.1.1)(utf-8-validate@5.0.10))(react@19.1.1) '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10) '@tanstack/react-query': 5.8.4(react-dom@19.1.1(react@19.1.1))(react-native@0.81.0(@babel/core@7.28.3)(@react-native/metro-config@0.81.0(@babel/core@7.28.3))(@types/react@18.3.23)(bufferutil@4.0.9)(react@19.1.1)(utf-8-validate@5.0.10))(react@19.1.1) '@trpc/client': 11.5.0(@trpc/server@11.5.0(typescript@5.9.2))(typescript@5.9.2) @@ -10453,48 +10351,6 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.1) @@ -10584,24 +10440,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 - '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/rect': 1.1.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -10631,35 +10469,6 @@ snapshots: '@types/react': 18.3.23 '@types/react-dom': 18.3.7(@types/react@18.3.23) - '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@19.1.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@18.3.23)(react@19.1.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - '@radix-ui/react-slider@1.3.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/number': 1.1.1 @@ -10726,13 +10535,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 - '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.23)(react@19.1.1)': - dependencies: - '@radix-ui/rect': 1.1.1 - react: 19.1.1 - optionalDependencies: - '@types/react': 18.3.23 - '@radix-ui/react-use-size@1.1.1(@types/react@18.3.23)(react@19.1.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.1) @@ -10740,17 +10542,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - - '@radix-ui/rect@1.1.1': {} - '@react-aria/focus@3.21.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@react-aria/interactions': 3.25.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -11878,11 +11669,11 @@ snapshots: '@wallet-standard/features': 1.1.0 eventemitter3: 5.0.1 - '@solana/wallet-adapter-react@0.15.39(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.0(@babel/core@7.28.3)(@react-native/metro-config@0.81.0(@babel/core@7.28.3))(@types/react@18.3.23)(bufferutil@4.0.9)(react@19.1.1)(utf-8-validate@5.0.10))(react@19.1.1)': + '@solana/wallet-adapter-react@0.15.39(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@6.0.0)(react-native@0.81.0(@babel/core@7.28.3)(@react-native/metro-config@0.81.0(@babel/core@7.28.3))(@types/react@18.3.23)(bufferutil@4.0.9)(react@19.1.1)(utf-8-validate@5.0.10))(react@19.1.1)': dependencies: '@solana-mobile/wallet-adapter-mobile': 2.2.3(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(react-native@0.81.0(@babel/core@7.28.3)(@react-native/metro-config@0.81.0(@babel/core@7.28.3))(@types/react@18.3.23)(bufferutil@4.0.9)(react@19.1.1)(utf-8-validate@5.0.10))(react@19.1.1) '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)) - '@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.1.1) + '@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@6.0.0)(react@19.1.1) '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10) react: 19.1.1 transitivePeerDependencies: @@ -11923,6 +11714,19 @@ snapshots: '@wallet-standard/wallet': 1.1.0 bs58: 5.0.0 + '@solana/wallet-standard-wallet-adapter-base@1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@6.0.0)': + dependencies: + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)) + '@solana/wallet-standard-chains': 1.1.1 + '@solana/wallet-standard-features': 1.3.0 + '@solana/wallet-standard-util': 1.1.2 + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10) + '@wallet-standard/app': 1.1.0 + '@wallet-standard/base': 1.1.0 + '@wallet-standard/features': 1.1.0 + '@wallet-standard/wallet': 1.1.0 + bs58: 6.0.0 + '@solana/wallet-standard-wallet-adapter-react@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.1.1)': dependencies: '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)) @@ -11934,6 +11738,17 @@ snapshots: - '@solana/web3.js' - bs58 + '@solana/wallet-standard-wallet-adapter-react@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@6.0.0)(react@19.1.1)': + dependencies: + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)) + '@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@6.0.0) + '@wallet-standard/app': 1.1.0 + '@wallet-standard/base': 1.1.0 + react: 19.1.1 + transitivePeerDependencies: + - '@solana/web3.js' + - bs58 + '@solana/wallet-standard-wallet-adapter@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.1.1)': dependencies: '@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10))(bs58@5.0.0) @@ -17396,11 +17211,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 - use-haptic@1.1.11: - dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - use-sidecar@1.1.3(@types/react@18.3.23)(react@19.1.1): dependencies: detect-node-es: 1.1.0 diff --git a/src/app/(mobile-ui)/home/page.tsx b/src/app/(mobile-ui)/home/page.tsx index 1801eceb1..bfa89bffc 100644 --- a/src/app/(mobile-ui)/home/page.tsx +++ b/src/app/(mobile-ui)/home/page.tsx @@ -38,9 +38,6 @@ import NoMoreJailModal from '@/components/Global/NoMoreJailModal' import EarlyUserModal from '@/components/Global/EarlyUserModal' import InvitesIcon from '@/components/Home/InvitesIcon' import NavigationArrow from '@/components/Global/NavigationArrow' -import KycCompletedModal from '@/components/Home/KycCompletedModal' -import { updateUserById } from '@/app/actions/users' -import { useHaptic } from 'use-haptic' const BALANCE_WARNING_THRESHOLD = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_THRESHOLD ?? '500') const BALANCE_WARNING_EXPIRY = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_EXPIRY ?? '1814400') // 21 days in seconds @@ -58,7 +55,6 @@ export default function Home() { }) const { isConnected: isWagmiConnected } = useAccount() const { disconnect: disconnectWagmi } = useDisconnect() - const { triggerHaptic } = useHaptic() const { isFetchingUser, addAccount } = useAuth() const { isUserKycApproved } = useKycStatus() @@ -68,7 +64,6 @@ export default function Home() { const [showBalanceWarningModal, setShowBalanceWarningModal] = useState(false) // const [showReferralCampaignModal, setShowReferralCampaignModal] = useState(false) const [isPostSignupActionModalVisible, setIsPostSignupActionModalVisible] = useState(false) - const [showKycModal, setShowKycModal] = useState(user?.user.showKycCompletedModal ?? false) const userFullName = useMemo(() => { if (!user) return @@ -175,7 +170,7 @@ export default function Home() {
- triggerHaptic()} href="/points" className="flex items-center gap-0"> + Points @@ -231,17 +226,6 @@ export default function Home() { - { - updateUserById({ - userId: user?.user.userId, - showKycCompletedModal: false, - }) - setShowKycModal(false) - }} - /> - {/* Balance Warning Modal */} -} diff --git a/src/app/(mobile-ui)/profile/identity-verification/[region]/page.tsx b/src/app/(mobile-ui)/profile/identity-verification/[region]/page.tsx deleted file mode 100644 index d1843f861..000000000 --- a/src/app/(mobile-ui)/profile/identity-verification/[region]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -'use client' -import RegionsPage from '@/components/Profile/views/RegionsPage.view' -import { useParams } from 'next/navigation' - -export default function IdentityVerificationRegionPage() { - const params = useParams() - const region = params.region as string - - return -} diff --git a/src/app/(mobile-ui)/profile/identity-verification/layout.tsx b/src/app/(mobile-ui)/profile/identity-verification/layout.tsx deleted file mode 100644 index 29884066e..000000000 --- a/src/app/(mobile-ui)/profile/identity-verification/layout.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client' - -import PageContainer from '@/components/0_Bruddle/PageContainer' -import ActionModal from '@/components/Global/ActionModal' -import { useIdentityVerification } from '@/hooks/useIdentityVerification' -import { useParams, useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' - -export default function IdentityVerificationLayout({ children }: { children: React.ReactNode }) { - const [isAlreadyVerifiedModalOpen, setIsAlreadyVerifiedModalOpen] = useState(false) - const router = useRouter() - const { isRegionAlreadyUnlocked, isVerifiedForCountry } = useIdentityVerification() - const params = useParams() - const regionParams = params.region as string - const countryParams = params.country as string - - useEffect(() => { - const isAlreadyVerified = - (countryParams && isVerifiedForCountry(countryParams)) || - (regionParams && isRegionAlreadyUnlocked(regionParams)) - - if (isAlreadyVerified) { - setIsAlreadyVerifiedModalOpen(true) - } - }, [countryParams, regionParams, isVerifiedForCountry, isRegionAlreadyUnlocked]) - - return ( - - {children} - - { - setIsAlreadyVerifiedModalOpen(false) - router.push('/profile') - }} - title="You're already verified" - description={ -

- Your identity has already been successfully verified for this region. You can continue to use - features available in this region. No further action is needed. -

- } - icon="shield" - ctas={[ - { - text: 'Close', - shadowSize: '4', - className: 'md:py-2', - onClick: () => { - setIsAlreadyVerifiedModalOpen(false) - router.push('/profile') - }, - }, - ]} - /> -
- ) -} diff --git a/src/app/(mobile-ui)/profile/identity-verification/page.tsx b/src/app/(mobile-ui)/profile/identity-verification/page.tsx index c19062338..904794567 100644 --- a/src/app/(mobile-ui)/profile/identity-verification/page.tsx +++ b/src/app/(mobile-ui)/profile/identity-verification/page.tsx @@ -1,5 +1,10 @@ -import RegionsVerification from '@/components/Profile/views/RegionsVerification.view' +import PageContainer from '@/components/0_Bruddle/PageContainer' +import IdentityVerificationView from '@/components/Profile/views/IdentityVerification.view' export default function IdentityVerificationPage() { - return + return ( + + + + ) } diff --git a/src/app/(mobile-ui)/withdraw/crypto/page.tsx b/src/app/(mobile-ui)/withdraw/crypto/page.tsx index 353643f71..e3db94dfc 100644 --- a/src/app/(mobile-ui)/withdraw/crypto/page.tsx +++ b/src/app/(mobile-ui)/withdraw/crypto/page.tsx @@ -29,7 +29,6 @@ import { captureMessage } from '@sentry/nextjs' import type { Address } from 'viem' import { Slider } from '@/components/Slider' import { tokenSelectorContext } from '@/context' -import { useHaptic } from 'use-haptic' export default function WithdrawCryptoPage() { const router = useRouter() @@ -65,7 +64,6 @@ export default function WithdrawCryptoPage() { isPreparingTx, reset: resetPaymentInitiator, } = usePaymentInitiator() - const { triggerHaptic } = useHaptic() // Helper to manage errors consistently const setError = useCallback( @@ -225,7 +223,6 @@ export default function WithdrawCryptoPage() { const result = await initiatePayment(paymentPayload) if (result.success && result.txHash) { - triggerHaptic() setCurrentView('STATUS') } else { console.error('Withdrawal execution failed:', result.error) diff --git a/src/app/(mobile-ui)/withdraw/manteca/page.tsx b/src/app/(mobile-ui)/withdraw/manteca/page.tsx index ac78f33d5..b2e61eac2 100644 --- a/src/app/(mobile-ui)/withdraw/manteca/page.tsx +++ b/src/app/(mobile-ui)/withdraw/manteca/page.tsx @@ -75,7 +75,7 @@ export default function MantecaWithdrawFlow() { const queryClient = useQueryClient() const { isUserBridgeKycApproved } = useKycStatus() const { hasPendingTransactions } = usePendingTransactions() - const swapCurrency = searchParams.get('swap-currency') ?? 'false' + // Get method and country from URL parameters const selectedMethodType = searchParams.get('method') // mercadopago, pix, bank-transfer, etc. const countryFromUrl = searchParams.get('country') // argentina, brazil, etc. @@ -431,7 +431,7 @@ export default function MantecaWithdrawFlow() { walletBalance={ balance ? formatAmount(formatUnits(balance, PEANUT_WALLET_TOKEN_DECIMALS)) : undefined } - isInitialInputUsd={swapCurrency !== 'true'} + isInitialInputUsd />
- } - /> - ) -} - -export default KycCompletedModal diff --git a/src/components/IdentityVerification/StartVerificationModal.tsx b/src/components/IdentityVerification/StartVerificationModal.tsx deleted file mode 100644 index 54b53b841..000000000 --- a/src/components/IdentityVerification/StartVerificationModal.tsx +++ /dev/null @@ -1,106 +0,0 @@ -'use client' - -import ActionModal from '../Global/ActionModal' -import InfoCard from '../Global/InfoCard' -import { Icon } from '../Global/Icons/Icon' -import { MantecaSupportedExchanges } from '../AddMoney/consts' -import { useMemo } from 'react' -import { useIdentityVerification } from '@/hooks/useIdentityVerification' - -interface StartVerificationModalProps { - visible: boolean - onClose: () => void - onStartVerification: () => void - selectedIdentityCountry: { id: string; title: string } - selectedCountry: { id: string; title: string } -} - -const StartVerificationModal = ({ - visible, - onClose, - onStartVerification, - selectedIdentityCountry, - selectedCountry, -}: StartVerificationModalProps) => { - const { getVerificationUnlockItems } = useIdentityVerification() - - const items = useMemo(() => { - return getVerificationUnlockItems(selectedIdentityCountry.title) - }, [getVerificationUnlockItems, selectedIdentityCountry.title]) - - const isIdentityMantecaCountry = useMemo( - () => Object.prototype.hasOwnProperty.call(MantecaSupportedExchanges, selectedIdentityCountry.id.toUpperCase()), - [selectedIdentityCountry.id] - ) - - const isSelectedCountryMantecaCountry = useMemo( - () => Object.prototype.hasOwnProperty.call(MantecaSupportedExchanges, selectedCountry.id.toUpperCase()), - [selectedCountry] - ) - - const getDescription = () => { - if (isSelectedCountryMantecaCountry && isIdentityMantecaCountry) { - return ( -

- To send and receive money locally, you'll need to verify your identity with a - government-issued ID from {selectedCountry.title}. -

- ) - } - - if (isSelectedCountryMantecaCountry && !isIdentityMantecaCountry) { - return `Without an ${selectedCountry.title} Issued ID, you can still pay in stores using QR codes but you won't be able to transfer money directly to bank accounts.` - } - - return ( -

- To make international money transfers, you must verify your identity using a government-issued - ID. -

- ) - } - - return ( - -

What you'll unlock:

- item.type === (isIdentityMantecaCountry ? 'manteca' : 'bridge')) - .map((item) => item.title)} - /> - -
- -

Peanut doesn't store any of your documents.

-
-
- } - /> - ) -} - -export default StartVerificationModal diff --git a/src/components/Invites/InvitesPage.tsx b/src/components/Invites/InvitesPage.tsx index 7a1c8f825..30baea391 100644 --- a/src/components/Invites/InvitesPage.tsx +++ b/src/components/Invites/InvitesPage.tsx @@ -15,7 +15,6 @@ import { useAuth } from '@/context/authContext' import { EInviteType } from '@/services/services.types' import { saveToCookie } from '@/utils' import { useLogin } from '@/hooks/useLogin' -import UnsupportedBrowserModal from '../Global/UnsupportedBrowserModal' function InvitePageContent() { const searchParams = useSearchParams() @@ -115,7 +114,6 @@ function InvitePageContent() {
- ) } diff --git a/src/components/Payment/PaymentForm/index.tsx b/src/components/Payment/PaymentForm/index.tsx index c9f3c7180..50105b7f7 100644 --- a/src/components/Payment/PaymentForm/index.tsx +++ b/src/components/Payment/PaymentForm/index.tsx @@ -41,7 +41,6 @@ import { EInviteType } from '@/services/services.types' import ContributorCard from '@/components/Global/Contributors/ContributorCard' import { getCardPosition } from '@/components/Global/Card' import * as Sentry from '@sentry/nextjs' -import { useHaptic } from 'use-haptic' export type PaymentFlowProps = { isExternalWalletFlow?: boolean @@ -158,7 +157,6 @@ export const PaymentForm = ({ const searchParams = useSearchParams() const requestId = searchParams.get('id') const isDepositRequest = searchParams.get('action') === 'deposit' - const { triggerHaptic } = useHaptic() const isUsingExternalWallet = useMemo(() => { return isExternalWalletFlow || !isPeanutWalletConnected @@ -496,7 +494,6 @@ export const PaymentForm = ({ const result = await initiatePayment(payload) if (result.status === 'Success') { - triggerHaptic() dispatch(paymentActions.setView('STATUS')) } else if (result.status === 'Charge Created') { if (!fulfillUsingManteca && !showRequestPotInitialView) { diff --git a/src/components/Payment/Views/Confirm.payment.view.tsx b/src/components/Payment/Views/Confirm.payment.view.tsx index 79e9f5fed..966b4009c 100644 --- a/src/components/Payment/Views/Confirm.payment.view.tsx +++ b/src/components/Payment/Views/Confirm.payment.view.tsx @@ -33,7 +33,6 @@ import { } from '@/constants' import { captureMessage } from '@sentry/nextjs' import AddressLink from '@/components/Global/AddressLink' -import { useHaptic } from 'use-haptic' type ConfirmPaymentViewProps = { currency?: { @@ -89,7 +88,6 @@ export default function ConfirmPaymentView({ const { isConnected: isWagmiConnected, address: wagmiAddress } = useAccount() const queryClient = useQueryClient() const [isRouteExpired, setIsRouteExpired] = useState(false) - const { triggerHaptic } = useHaptic() const isUsingExternalWallet = isExternalWalletFlow || !isPeanutWallet @@ -284,7 +282,6 @@ export default function ConfirmPaymentView({ fetchBalance() queryClient.invalidateQueries({ queryKey: [TRANSACTIONS] }) }, 3000) - triggerHaptic() dispatch(paymentActions.setView('STATUS')) } }, [ diff --git a/src/components/Payment/Views/Status.payment.view.tsx b/src/components/Payment/Views/Status.payment.view.tsx index 91ca85a28..2be1980f6 100644 --- a/src/components/Payment/Views/Status.payment.view.tsx +++ b/src/components/Payment/Views/Status.payment.view.tsx @@ -1,4 +1,5 @@ 'use client' +import { PEANUT_LOGO_BLACK, PEANUTMAN_LOGO } from '@/assets' import { Button } from '@/components/0_Bruddle' import AddressLink from '@/components/Global/AddressLink' import Card from '@/components/Global/Card' @@ -25,8 +26,6 @@ import { type ReactNode, useEffect, useMemo, useRef } from 'react' import { useDispatch } from 'react-redux' import STAR_STRAIGHT_ICON from '@/assets/icons/starStraight.svg' import { usePointsConfetti } from '@/hooks/usePointsConfetti' -import chillPeanutAnim from '@/animations/GIF_ALPHA_BACKGORUND/512X512_ALPHA_GIF_konradurban_01.gif' -import { useHaptic } from 'use-haptic' type DirectSuccessViewProps = { user?: ApiUser @@ -64,7 +63,6 @@ const DirectSuccessView = ({ useTransactionDetailsDrawer() const { user: authUser } = useUserStore() const queryClient = useQueryClient() - const { triggerHaptic } = useHaptic() const { tokenIconUrl, chainIconUrl, resolvedChainName, resolvedTokenSymbol } = useTokenChainIcons({ chainId: chargeDetails?.chainId, @@ -190,11 +188,6 @@ const DirectSuccessView = ({ if (type === 'REQUEST') return 'You requested ' } - useEffect(() => { - // trigger haptic on mount - triggerHaptic() - }, [triggerHaptic]) - return (
@@ -210,14 +203,7 @@ const DirectSuccessView = ({ />
)} -
- Peanut Mascot +
void - rightContent?: (country: CountryData & { isSupported?: boolean }, index: number) => ReactNode - isDisabled?: boolean - value: string - defaultOpen?: boolean -} - -const CountryListSection = ({ - title, - description, - countries, - onCountryClick, - rightContent, - isDisabled = false, - value, - defaultOpen = false, -}: CountryListSectionProps) => { - return ( - - - -

{title}

- -
-
- {description &&

{description}

} - - {countries.map((country, index) => { - const position = getCardPosition(index, countries.length) - return ( - onCountryClick(country, index)} - position={position} - isDisabled={isDisabled} - leftIcon={ -
- {`${country.title} { - e.currentTarget.style.display = 'none' - }} - /> -
- } - /> - ) - })} -
-
- ) -} - -export default CountryListSection diff --git a/src/components/Profile/components/IdentityVerificationCountryList.tsx b/src/components/Profile/components/IdentityVerificationCountryList.tsx deleted file mode 100644 index bc6b0416b..000000000 --- a/src/components/Profile/components/IdentityVerificationCountryList.tsx +++ /dev/null @@ -1,70 +0,0 @@ -'use client' -import StatusBadge from '@/components/Global/Badges/StatusBadge' -import { Icon } from '@/components/Global/Icons/Icon' -import { SearchInput } from '@/components/SearchInput' -import { getCountriesForRegion } from '@/utils/identityVerification' -import * as Accordion from '@radix-ui/react-accordion' -import { useRouter } from 'next/navigation' -import { useState } from 'react' -import CountryListSection from './CountryListSection' - -const IdentityVerificationCountryList = ({ region }: { region: string }) => { - const [searchTerm, setSearchTerm] = useState('') - const router = useRouter() - - const { supportedCountries, unsupportedCountries } = getCountriesForRegion(region) - - // Filter both arrays based on search term - const filteredSupportedCountries = supportedCountries.filter((country) => - country.title.toLowerCase().includes(searchTerm.toLowerCase()) - ) - - const filteredUnsupportedCountries = unsupportedCountries.filter((country) => - country.title.toLowerCase().includes(searchTerm.toLowerCase()) - ) - - const isLatam = region === 'latam' - - return ( -
-
- setSearchTerm(e.target.value)} - onClear={() => setSearchTerm('')} - placeholder="Search by country name" - /> -
- - - { - if (isLatam) { - router.push(`/profile/identity-verification/${region}/${encodeURIComponent(country.id)}`) - } else { - router.push(`/profile/identity-verification/${region}/${encodeURIComponent('bridge')}`) - } - }} - rightContent={() => (isLatam ? undefined : )} - defaultOpen - /> - - {}} - rightContent={() => } - defaultOpen - isDisabled - /> - -
- ) -} - -export default IdentityVerificationCountryList diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx index 2bf981fb3..812fa2846 100644 --- a/src/components/Profile/index.tsx +++ b/src/components/Profile/index.tsx @@ -25,7 +25,7 @@ export const Profile = () => { const [isInviteFriendsModalOpen, setIsInviteFriendsModalOpen] = useState(false) const [showInitiateKycModal, setShowInitiateKycModal] = useState(false) const router = useRouter() - const { isUserKycApproved } = useKycStatus() + const { isUserKycApproved, isUserBridgeKycUnderReview } = useKycStatus() const logout = async () => { await logoutUser() @@ -55,6 +55,15 @@ export const Profile = () => {
+ {/* Menu Item - Invite Entry */} + {/* Enable with Invites project. */} + {/* */} {
{ setShowInitiateKycModal(true) }} position="middle" + endIcon={isUserKycApproved ? 'check' : undefined} + endIconClassName={isUserKycApproved ? 'text-success-3 size-4' : undefined} /> diff --git a/src/components/Profile/views/IdentityVerification.view.tsx b/src/components/Profile/views/IdentityVerification.view.tsx index d82e869ee..1e6376e9c 100644 --- a/src/components/Profile/views/IdentityVerification.view.tsx +++ b/src/components/Profile/views/IdentityVerification.view.tsx @@ -1,25 +1,26 @@ 'use client' import { updateUserById } from '@/app/actions/users' import { Button } from '@/components/0_Bruddle' -import { BRIDGE_ALPHA3_TO_ALPHA2, countryData } from '@/components/AddMoney/consts' +import { BRIDGE_ALPHA3_TO_ALPHA2, MantecaSupportedExchanges } from '@/components/AddMoney/consts' import { UserDetailsForm, type UserDetailsFormData } from '@/components/AddMoney/UserDetailsForm' import { CountryList } from '@/components/Common/CountryList' import ErrorAlert from '@/components/Global/ErrorAlert' import IframeWrapper from '@/components/Global/IframeWrapper' import NavHeader from '@/components/Global/NavHeader' +import ActionModal from '@/components/Global/ActionModal' import { KycVerificationInProgressModal, PeanutDoesntStoreAnyPersonalInformation, } from '@/components/Kyc/KycVerificationInProgressModal' import { MantecaGeoSpecificKycModal } from '@/components/Kyc/InitiateMantecaKYCModal' +import { Icon } from '@/components/Global/Icons/Icon' import { useAuth } from '@/context/authContext' import { useBridgeKycFlow } from '@/hooks/useBridgeKycFlow' -import { useParams, useRouter } from 'next/navigation' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { MantecaKycStatus } from '@/interfaces' +import { useRouter } from 'next/navigation' +import { useCallback, useMemo, useRef, useState } from 'react' import useKycStatus from '@/hooks/useKycStatus' import { getRedirectUrl, clearRedirectUrl } from '@/utils/general.utils' -import StartVerificationModal from '@/components/IdentityVerification/StartVerificationModal' -import { useIdentityVerification } from '@/hooks/useIdentityVerification' const IdentityVerificationView = () => { const router = useRouter() @@ -28,15 +29,12 @@ const IdentityVerificationView = () => { const [isUpdatingUser, setIsUpdatingUser] = useState(false) const [userUpdateError, setUserUpdateError] = useState(null) const [showUserDetailsForm, setShowUserDetailsForm] = useState(false) + const [isAlreadyVerifiedModalOpen, setIsAlreadyVerifiedModalOpen] = useState(false) const [isMantecaModalOpen, setIsMantecaModalOpen] = useState(false) const [selectedCountry, setSelectedCountry] = useState<{ id: string; title: string } | null>(null) const [userClickedCountry, setUserClickedCountry] = useState<{ id: string; title: string } | null>(null) const { isUserBridgeKycApproved } = useKycStatus() const { user, fetchUser } = useAuth() - const [isStartVerificationModalOpen, setIsStartVerificationModalOpen] = useState(false) - const params = useParams() - const countryParam = params.country as string - const { isMantecaSupportedCountry, isBridgeSupportedCountry } = useIdentityVerification() const handleRedirect = () => { const redirectUrl = getRedirectUrl() @@ -44,7 +42,7 @@ const IdentityVerificationView = () => { clearRedirectUrl() router.push(redirectUrl) } else { - router.push('/profile') + router.replace('/profile') } } @@ -108,32 +106,36 @@ const IdentityVerificationView = () => { } }, [showUserDetailsForm]) - // Memoized country lookup from URL param - const selectedCountryParams = useMemo(() => { - if (countryParam) { - const country = countryData.find((country) => country.id.toUpperCase() === countryParam.toUpperCase()) - if (country) { - return country - } else { - return { title: 'Bridge', id: 'bridge', type: 'bridge', description: '', path: 'bridge' } - } - } - return null - }, [countryParam]) - - // Skip country selection if coming from a supported bridge country - useEffect(() => { - if (selectedCountryParams && (isBridgeSupportedCountry(countryParam) || countryParam === 'bridge')) { - setUserClickedCountry({ title: selectedCountryParams.title, id: selectedCountryParams.id }) - setIsStartVerificationModalOpen(true) - } - }, [countryParam, isBridgeSupportedCountry, selectedCountryParams]) + // country validation helpers + const isBridgeSupportedCountry = (code: string) => { + const upper = code.toUpperCase() + return ( + upper === 'US' || + upper === 'MX' || + Object.keys(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper) || + Object.values(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper) + ) + } - useEffect(() => { - return () => { - setIsStartVerificationModalOpen(false) - } - }, []) + const isMantecaSupportedCountry = (code: string) => { + const upper = code.toUpperCase() + return Object.prototype.hasOwnProperty.call(MantecaSupportedExchanges, upper) + } + + const isVerifiedForCountry = useCallback( + (code: string) => { + const upper = code.toUpperCase() + const mantecaActive = + user?.user.kycVerifications?.some( + (v) => + v.provider === 'MANTECA' && + (v.mantecaGeo || '').toUpperCase() === upper && + v.status === MantecaKycStatus.ACTIVE + ) ?? false + return isMantecaSupportedCountry(upper) ? mantecaActive : isUserBridgeKycApproved + }, + [user] + ) return (
@@ -180,19 +182,57 @@ const IdentityVerificationView = () => { ) : (
+ isVerifiedForCountry(country.id) ? ( + + ) : undefined + } onCountryClick={(country) => { const { id, title } = country setUserClickedCountry({ id, title }) - setIsStartVerificationModalOpen(true) + + if (isVerifiedForCountry(id)) { + setIsAlreadyVerifiedModalOpen(true) + return + } + + if (isMantecaSupportedCountry(id)) { + setSelectedCountry({ id, title }) + setIsMantecaModalOpen(true) + } else { + setShowUserDetailsForm(true) + } }} - showLoadingState={false} // we don't want to show loading state when clicking a country, here because there is no async operation when clicking a country />
)} + setIsAlreadyVerifiedModalOpen(false)} + title="You're already verified" + description={ +

+ Your identity has already been successfully verified for {userClickedCountry?.title}. You can + continue to use features available in this region. No further action is needed. +

+ } + icon="shield" + ctas={[ + { + text: 'Close', + shadowSize: '4', + className: 'md:py-2', + onClick: () => { + setIsAlreadyVerifiedModalOpen(false) + handleRedirect() + }, + }, + ]} + /> + {selectedCountry && ( { onKycSuccess={handleRedirect} /> )} - - {userClickedCountry && selectedCountryParams && ( - { - // we dont show ID issuer country list for bridge countries - if ( - isBridgeSupportedCountry(selectedCountryParams.id) || - selectedCountryParams.id === 'bridge' - ) { - handleRedirect() - } else { - setIsStartVerificationModalOpen(false) - } - }} - onStartVerification={() => { - setIsStartVerificationModalOpen(false) - if (isMantecaSupportedCountry(userClickedCountry.id)) { - setSelectedCountry(userClickedCountry) - setIsMantecaModalOpen(true) - } else { - setShowUserDetailsForm(true) - } - }} - selectedIdentityCountry={userClickedCountry} - selectedCountry={selectedCountryParams} - /> - )}
) } diff --git a/src/components/Profile/views/RegionsPage.view.tsx b/src/components/Profile/views/RegionsPage.view.tsx deleted file mode 100644 index a02fb1720..000000000 --- a/src/components/Profile/views/RegionsPage.view.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client' - -import NavHeader from '@/components/Global/NavHeader' -import { useIdentityVerification } from '@/hooks/useIdentityVerification' -import { useRouter } from 'next/navigation' -import React from 'react' -import IdentityVerificationCountryList from '../components/IdentityVerificationCountryList' -import { Button } from '@/components/0_Bruddle' - -const RegionsPage = ({ path }: { path: string }) => { - const router = useRouter() - const { lockedRegions } = useIdentityVerification() - - const region = lockedRegions.find((region) => region.path === path) - - if (!region) { - return null - } - - return ( -
-
- router.back()} /> - - -
- {region.path !== 'latam' && ( -
- -
- )} -
- ) -} - -export default RegionsPage diff --git a/src/components/Profile/views/RegionsVerification.view.tsx b/src/components/Profile/views/RegionsVerification.view.tsx deleted file mode 100644 index 056d4be63..000000000 --- a/src/components/Profile/views/RegionsVerification.view.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client' - -import { ActionListCard } from '@/components/ActionListCard' -import { getCardPosition } from '@/components/Global/Card' -import EmptyState from '@/components/Global/EmptyStates/EmptyState' -import { Icon } from '@/components/Global/Icons/Icon' -import NavHeader from '@/components/Global/NavHeader' -import { useIdentityVerification, type Region } from '@/hooks/useIdentityVerification' -import Image from 'next/image' -import { useRouter } from 'next/navigation' -import React from 'react' - -const RegionsVerification = () => { - const router = useRouter() - const { unlockedRegions, lockedRegions } = useIdentityVerification() - - return ( -
- router.replace('/profile')} /> -
-

Unlocked regions

-

- Transfer to and receive from any bank account and use supported payments methods. -

- - {unlockedRegions.length === 0 && ( - - )} - - - -

Locked regions

-

Where do you want to send and receive money?

- - -
-
- ) -} - -export default RegionsVerification - -interface RegionsListProps { - regions: Region[] - isLocked: boolean -} -const RegionsList = ({ regions, isLocked }: RegionsListProps) => { - const router = useRouter() - return ( -
- {regions.map((region, index) => ( - - } - position={getCardPosition(index, regions.length)} - title={region.name} - onClick={() => { - if (isLocked) { - router.push(`/profile/identity-verification/${region.path}`) - } - }} - description={region.description} - descriptionClassName="text-xs" - rightContent={!isLocked ? : null} - /> - ))} -
- ) -} diff --git a/src/components/TransactionDetails/TransactionCard.tsx b/src/components/TransactionDetails/TransactionCard.tsx index 2f83a8daf..bc95da8e0 100644 --- a/src/components/TransactionDetails/TransactionCard.tsx +++ b/src/components/TransactionDetails/TransactionCard.tsx @@ -21,7 +21,6 @@ import { VerifiedUserLabel } from '../UserHeader' import { isAddress } from 'viem' import { EHistoryEntryType } from '@/utils/history.utils' import { PerkIcon } from './PerkIcon' -import { useHaptic } from 'use-haptic' export type TransactionType = | 'send' @@ -68,10 +67,8 @@ const TransactionCard: React.FC = ({ // hook to manage the state of the details drawer (open/closed, selected transaction) const { isDrawerOpen, selectedTransaction, openTransactionDetails, closeTransactionDetails } = useTransactionDetailsDrawer() - const { triggerHaptic } = useHaptic() const handleClick = () => { - triggerHaptic() openTransactionDetails(transaction) } diff --git a/src/constants/index.ts b/src/constants/index.ts index f4a42c5f7..3670e535e 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -7,4 +7,3 @@ export * from './query.consts' export * from './zerodev.consts' export * from './manteca.consts' export * from './routes' -export * from './stateCodes.consts' diff --git a/src/constants/stateCodes.consts.ts b/src/constants/stateCodes.consts.ts deleted file mode 100644 index d8ae2987f..000000000 --- a/src/constants/stateCodes.consts.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * US States with ISO 3166-2 subdivision codes - * Format: US-XX where XX is the two-letter state code - */ -export const US_STATES = [ - { name: 'Alabama', code: 'AL' }, - { name: 'Alaska', code: 'AK' }, - { name: 'Arizona', code: 'AZ' }, - { name: 'Arkansas', code: 'AR' }, - { name: 'California', code: 'CA' }, - { name: 'Colorado', code: 'CO' }, - { name: 'Connecticut', code: 'CT' }, - { name: 'Delaware', code: 'DE' }, - { name: 'Florida', code: 'FL' }, - { name: 'Georgia', code: 'GA' }, - { name: 'Hawaii', code: 'HI' }, - { name: 'Idaho', code: 'ID' }, - { name: 'Illinois', code: 'IL' }, - { name: 'Indiana', code: 'IN' }, - { name: 'Iowa', code: 'IA' }, - { name: 'Kansas', code: 'KS' }, - { name: 'Kentucky', code: 'KY' }, - { name: 'Louisiana', code: 'LA' }, - { name: 'Maine', code: 'ME' }, - { name: 'Maryland', code: 'MD' }, - { name: 'Massachusetts', code: 'MA' }, - { name: 'Michigan', code: 'MI' }, - { name: 'Minnesota', code: 'MN' }, - { name: 'Mississippi', code: 'MS' }, - { name: 'Missouri', code: 'MO' }, - { name: 'Montana', code: 'MT' }, - { name: 'Nebraska', code: 'NE' }, - { name: 'Nevada', code: 'NV' }, - { name: 'New Hampshire', code: 'NH' }, - { name: 'New Jersey', code: 'NJ' }, - { name: 'New Mexico', code: 'NM' }, - { name: 'New York', code: 'NY' }, - { name: 'North Carolina', code: 'NC' }, - { name: 'North Dakota', code: 'ND' }, - { name: 'Ohio', code: 'OH' }, - { name: 'Oklahoma', code: 'OK' }, - { name: 'Oregon', code: 'OR' }, - { name: 'Pennsylvania', code: 'PA' }, - { name: 'Rhode Island', code: 'RI' }, - { name: 'South Carolina', code: 'SC' }, - { name: 'South Dakota', code: 'SD' }, - { name: 'Tennessee', code: 'TN' }, - { name: 'Texas', code: 'TX' }, - { name: 'Utah', code: 'UT' }, - { name: 'Vermont', code: 'VT' }, - { name: 'Virginia', code: 'VA' }, - { name: 'Washington', code: 'WA' }, - { name: 'West Virginia', code: 'WV' }, - { name: 'Wisconsin', code: 'WI' }, - { name: 'Wyoming', code: 'WY' }, -] as const - -export type USStateCode = (typeof US_STATES)[number]['code'] - -/** - * Mexican States with ISO 3166-2 subdivision codes - * Format: MX-XXX where XXX is the three-letter state code - */ -export const MX_STATES = [ - { name: 'Aguascalientes', code: 'AGU' }, - { name: 'Baja California', code: 'BCN' }, - { name: 'Baja California Sur', code: 'BCS' }, - { name: 'Campeche', code: 'CAM' }, - { name: 'Chiapas', code: 'CHP' }, - { name: 'Chihuahua', code: 'CHH' }, - { name: 'Ciudad de México', code: 'CMX' }, - { name: 'Coahuila', code: 'COA' }, - { name: 'Colima', code: 'COL' }, - { name: 'Durango', code: 'DUR' }, - { name: 'Guanajuato', code: 'GUA' }, - { name: 'Guerrero', code: 'GRO' }, - { name: 'Hidalgo', code: 'HID' }, - { name: 'Jalisco', code: 'JAL' }, - { name: 'México', code: 'MEX' }, - { name: 'Michoacán', code: 'MIC' }, - { name: 'Morelos', code: 'MOR' }, - { name: 'Nayarit', code: 'NAY' }, - { name: 'Nuevo León', code: 'NLE' }, - { name: 'Oaxaca', code: 'OAX' }, - { name: 'Puebla', code: 'PUE' }, - { name: 'Querétaro', code: 'QUE' }, - { name: 'Quintana Roo', code: 'ROO' }, - { name: 'San Luis Potosí', code: 'SLP' }, - { name: 'Sinaloa', code: 'SIN' }, - { name: 'Sonora', code: 'SON' }, - { name: 'Tabasco', code: 'TAB' }, - { name: 'Tamaulipas', code: 'TAM' }, - { name: 'Tlaxcala', code: 'TLA' }, - { name: 'Veracruz', code: 'VER' }, - { name: 'Yucatán', code: 'YUC' }, - { name: 'Zacatecas', code: 'ZAC' }, -] as const - -export type MXStateCode = (typeof MX_STATES)[number]['code'] diff --git a/src/context/contextProvider.tsx b/src/context/contextProvider.tsx index c1d8689fe..d285c51e7 100644 --- a/src/context/contextProvider.tsx +++ b/src/context/contextProvider.tsx @@ -10,7 +10,6 @@ import { ClaimBankFlowContextProvider } from './ClaimBankFlowContext' import { RequestFulfilmentFlowContextProvider } from './RequestFulfillmentFlowContext' import { SupportModalProvider } from './SupportModalContext' import { QrCodeProvider } from './QrCodeContext' -import { PasskeySupportProvider } from './passkeySupportContext' export const ContextProvider = ({ children }: { children: React.ReactNode }) => { return ( @@ -25,9 +24,7 @@ export const ContextProvider = ({ children }: { children: React.ReactNode }) => - - {children} - + {children} diff --git a/src/context/passkeySupportContext.tsx b/src/context/passkeySupportContext.tsx deleted file mode 100644 index 110314b86..000000000 --- a/src/context/passkeySupportContext.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client' -import { usePasskeySupport, type PasskeySupportResult } from '@/hooks/usePasskeySupport' -import { createContext, useContext } from 'react' - -const PasskeySupportContext = createContext(null) - -export const PasskeySupportProvider = ({ children }: { children: React.ReactNode }) => { - const support = usePasskeySupport() - return {children} -} - -export const usePasskeySupportContext = () => { - const context = useContext(PasskeySupportContext) - if (!context) throw new Error('usePasskeySupportContext must be used within PasskeySupportProvider') - return context -} diff --git a/src/hooks/useIdentityVerification.tsx b/src/hooks/useIdentityVerification.tsx deleted file mode 100644 index a6b4e7ed5..000000000 --- a/src/hooks/useIdentityVerification.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import { EUROPE_GLOBE_ICON, LATAM_GLOBE_ICON, NORTH_AMERICA_GLOBE_ICON, REST_OF_WORLD_GLOBE_ICON } from '@/assets' -import type { StaticImageData } from 'next/image' -import useKycStatus from './useKycStatus' -import { useMemo, useCallback } from 'react' -import { useAuth } from '@/context/authContext' -import { MantecaKycStatus } from '@/interfaces' -import { BRIDGE_ALPHA3_TO_ALPHA2, MantecaSupportedExchanges, countryData } from '@/components/AddMoney/consts' -import React from 'react' - -/** Represents a geographic region with its display information */ -export type Region = { - path: string - name: string - icon: StaticImageData | string - description?: string -} - -/** Represents a feature that gets unlocked after identity verification */ -export type VerificationUnlockItem = { - title: React.ReactNode | string - type: 'bridge' | 'manteca' -} - -// Manteca handles LATAM countries (Argentina, Brazil, Mexico, etc.) -const MANTECA_SUPPORTED_REGIONS = ['LATAM'] - -// Bridge handles North America and Europe -const BRIDGE_SUPPORTED_REGIONS = ['North America', 'Europe'] - -const SUPPORTED_REGIONS: Region[] = [ - { - path: 'europe', - name: 'Europe', - icon: EUROPE_GLOBE_ICON, - }, - { - path: 'north-america', - name: 'North America', - icon: NORTH_AMERICA_GLOBE_ICON, - }, - { - path: 'latam', - name: 'LATAM', - icon: LATAM_GLOBE_ICON, - }, - { - path: 'rest-of-the-world', - name: 'Rest of the world', - icon: REST_OF_WORLD_GLOBE_ICON, - }, -] - -// Special case: Users with Bridge KYC can do QR payments in these countries -// even without full Manteca KYC -const MANTECA_QR_ONLY_REGIONS: Region[] = [ - { - path: 'argentina', - name: 'Argentina', - icon: 'https://flagcdn.com/w160/ar.png', - description: 'Only Mercado Pago QR payments', - }, - { - path: 'brazil', - name: 'Brazil', - icon: 'https://flagcdn.com/w160/br.png', - description: 'Only PIX QR payments', - }, -] - -const BRIDGE_SUPPORTED_LATAM_COUNTRIES: Region[] = [ - { - path: 'mexico', - name: 'Mexico', - icon: 'https://flagcdn.com/w160/mx.png', - }, -] - -/** - * Hook for managing identity verification (KYC) status and region access. - * - * This hook handles two KYC providers: - * - Bridge: Covers North America and Europe (ACH, Wire, SEPA transfers) - * - Manteca: Covers LATAM countries (Bank transfers + QR payments) - * - * Users can complete one or both KYC processes to unlock different regions. - * Special case: Bridge KYC also unlocks QR payments in Argentina and Brazil. - * - * @returns {Object} Identity verification utilities - * @returns {Region[]} lockedRegions - Regions the user hasn't unlocked yet - * @returns {Region[]} unlockedRegions - Regions the user has access to - * @returns {Function} isMantecaSupportedCountry - Check if a country uses Manteca - * @returns {Function} isVerifiedForCountry - Check if user is verified for a specific country - * @returns {Function} isRegionAlreadyUnlocked - Check if a region path is already unlocked - * @returns {Function} getCountryTitle - Get the display name for a country code - * @returns {Function} getVerificationUnlockItems - Get list of features unlocked by verification - */ -export const useIdentityVerification = () => { - const { user } = useAuth() - const { isUserBridgeKycApproved, isUserMantecaKycApproved } = useKycStatus() - - /** - * Check if a country is supported by Manteca (LATAM countries). - * @param code - Country code (e.g., 'AR', 'BR', 'MX') - * @returns true if the country is supported by Manteca - */ - const isMantecaSupportedCountry = useCallback((code: string) => { - const upper = code.toUpperCase() - return Object.prototype.hasOwnProperty.call(MantecaSupportedExchanges, upper) - }, []) - - /** - * Check if the user is verified for a specific country. - * - * Logic: - * - For Manteca countries: User must have an ACTIVE Manteca verification for that specific country - * - For Bridge countries: User just needs Bridge KYC approval - * - * @param code - Country code (e.g., 'US', 'GB', 'AR') - * @returns true if user has the required verification - */ - const isVerifiedForCountry = useCallback( - (code: string) => { - 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] - ) - - /** - * Calculate which regions are locked vs unlocked for the current user. - * - * Region unlock logic: - * - Bridge KYC → Unlocks North America, Mexico & Europe - * - Manteca KYC → Unlocks LATAM - * - Bridge KYC (without Manteca) → Also gets QR-only access to Argentina & Brazil - * - */ - const { lockedRegions, unlockedRegions } = useMemo(() => { - const isBridgeApproved = isUserBridgeKycApproved - const isMantecaApproved = isUserMantecaKycApproved - - // Helper to check if a region should be unlocked - const isRegionUnlocked = (regionName: string) => { - return ( - (isBridgeApproved && BRIDGE_SUPPORTED_REGIONS.includes(regionName)) || - (isMantecaApproved && MANTECA_SUPPORTED_REGIONS.includes(regionName)) - ) - } - - const unlocked = SUPPORTED_REGIONS.filter((region) => isRegionUnlocked(region.name)) - const locked = SUPPORTED_REGIONS.filter((region) => !isRegionUnlocked(region.name)) - - // Bridge users get QR payment access in Argentina & Brazil - // even without full Manteca KYC (which unlocks bank transfers too) - if (isBridgeApproved && !isMantecaApproved) { - unlocked.push(...MANTECA_QR_ONLY_REGIONS, ...BRIDGE_SUPPORTED_LATAM_COUNTRIES) - } - - return { - lockedRegions: locked, - unlockedRegions: unlocked, - } - }, [isUserBridgeKycApproved, isUserMantecaKycApproved]) - - /** - * Check if a region is already unlocked by comparing region paths. - * @param regionPath - Region path to check (e.g., 'north-america', 'latam') - * @returns true if the region is in the user's unlocked regions - */ - const isRegionAlreadyUnlocked = useCallback( - (regionPath: string) => { - return unlockedRegions.some((region) => region.path.toLowerCase() === regionPath.toLowerCase()) - }, - [unlockedRegions] - ) - - /** - * Get the human-readable country name from a country code. - * @param countryCode - ISO country code (e.g., 'US', 'BR', 'AR') - * @returns Country display name or null if not found - */ - const getCountryTitle = useCallback((countryCode: string) => { - return countryData.find((country) => country.id.toUpperCase() === countryCode.toUpperCase())?.title ?? null - }, []) - - /** - * Get a list of features that will be unlocked after verification. - * Used to show users what they'll gain access to. - * - * @param countryTitle - Optional country name to personalize the messaging - * @returns Array of unlock items with title and which KYC provider unlocks it - */ - const getVerificationUnlockItems = useCallback((countryTitle?: string): VerificationUnlockItem[] => { - return [ - { - title: ( -

- QR Payments in Argentina and Brazil -

- ), - type: 'bridge', - }, - { - title: ( -

- United States ACH and Wire transfers -

- ), - type: 'bridge', - }, - { - title: ( -

- Europe SEPA transfers (+30 countries) -

- ), - type: 'bridge', - }, - { - title: ( -

- Mexico SPEI transfers -

- ), - type: 'bridge', - }, - { - // Important: This uses the user's verified ID country, not their selected country - // Example: User picks Argentina but has Brazil ID → they get QR in Argentina - // but bank transfers only work in Brazil (their verified country) - title: `Bank transfers to your own accounts in ${countryTitle || 'your country'}`, - type: 'manteca', - }, - { - title: 'QR Payments in Brazil and Argentina', - type: 'manteca', - }, - ] - }, []) - - const isBridgeSupportedCountry = (code: string) => { - const upper = code.toUpperCase() - return ( - upper === 'US' || - upper === 'MX' || - Object.keys(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper) || - Object.values(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper) - ) - } - - return { - lockedRegions, - unlockedRegions, - isMantecaSupportedCountry, - isVerifiedForCountry, - isRegionAlreadyUnlocked, - getCountryTitle, - getVerificationUnlockItems, - isBridgeSupportedCountry, - } -} diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index f2991ce66..6ba29eeef 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -258,7 +258,6 @@ export interface User { showFullName: boolean createdAt: string accounts: Account[] - showKycCompletedModal: boolean badges?: Array<{ id?: string code: string diff --git a/src/utils/__mocks__/simplewebauthn-browser.ts b/src/utils/__mocks__/simplewebauthn-browser.ts deleted file mode 100644 index 18179aa0e..000000000 --- a/src/utils/__mocks__/simplewebauthn-browser.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Mock for @simplewebauthn/browser -export const browserSupportsWebAuthn = jest.fn(() => true) - -export const browserSupportsWebAuthnAutofill = jest.fn(() => Promise.resolve(true)) - -export const startRegistration = jest.fn(() => - Promise.resolve({ - id: 'mock-credential-id', - rawId: 'mock-raw-id', - response: { - clientDataJSON: 'mock-client-data', - attestationObject: 'mock-attestation', - }, - type: 'public-key', - }) -) - -export const startAuthentication = jest.fn(() => - Promise.resolve({ - id: 'mock-credential-id', - rawId: 'mock-raw-id', - response: { - clientDataJSON: 'mock-client-data', - authenticatorData: 'mock-auth-data', - signature: 'mock-signature', - userHandle: 'mock-user-handle', - }, - type: 'public-key', - }) -) - -export const platformAuthenticatorIsAvailable = jest.fn(() => Promise.resolve(true)) - -export const base64URLStringToBuffer = jest.fn((base64URLString: string) => { - return new ArrayBuffer(8) -}) - -export const bufferToBase64URLString = jest.fn((buffer: ArrayBuffer) => { - return 'mock-base64-string' -}) - -export class WebAuthnError extends Error { - constructor(message: string) { - super(message) - this.name = 'WebAuthnError' - } -} - -export class WebAuthnAbortService { - static createNewAbortSignal() { - return new AbortController().signal - } -} diff --git a/src/utils/identityVerification.tsx b/src/utils/identityVerification.tsx deleted file mode 100644 index 3f6abb80f..000000000 --- a/src/utils/identityVerification.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ALL_COUNTRIES_ALPHA3_TO_ALPHA2, countryData, MEXICO_ALPHA3_TO_ALPHA2 } from '@/components/AddMoney/consts' - -export const getCountriesForRegion = (region: string) => { - const supportedCountriesIso3 = Object.keys(ALL_COUNTRIES_ALPHA3_TO_ALPHA2).concat( - Object.keys(MEXICO_ALPHA3_TO_ALPHA2) // Add Mexico as well, supported by bridge - ) - - const countries = countryData.filter((country) => country.region === region) - - const supportedCountries = [] - const unsupportedCountries = [] - - for (const country of countries) { - if (country.iso3 && supportedCountriesIso3.includes(country.iso3)) { - supportedCountries.push({ ...country, isSupported: true }) - } else { - unsupportedCountries.push({ ...country, isSupported: false }) - } - } - - return { supportedCountries, unsupportedCountries } -} diff --git a/tailwind.config.js b/tailwind.config.js index 55292eab7..904c97c7b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -197,14 +197,6 @@ module.exports = { '0%, 50%': { opacity: '1' }, '50.01%, 100%': { opacity: '0' }, }, - 'accordion-down': { - from: { height: '0' }, - to: { height: 'var(--radix-accordion-content-height)' }, - }, - 'accordion-up': { - from: { height: 'var(--radix-accordion-content-height)' }, - to: { height: '0' }, - }, starPulsateWiggle: { // Gentle pulsate 3 times '0%': { transform: 'scale(1) rotate(0deg)' }, @@ -234,8 +226,6 @@ module.exports = { 'pulsate-slow': 'pulsateDeep 4s ease-in-out infinite', 'pulse-strong': 'pulse-strong 1s ease-in-out infinite', blink: 'blink 1.5s step-end infinite', - 'accordion-down': 'accordion-down 0.3s cubic-bezier(0.87, 0, 0.13, 1)', - 'accordion-up': 'accordion-up 0.3s cubic-bezier(0.87, 0, 0.13, 1)', 'star-pulsate-wiggle': 'starPulsateWiggle 10s ease-in-out infinite', }, opacity: {