diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..600c998 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(awk 'NR>=10 && NR<=46 {printf \"%d:%s$\\\\n\", NR, $0}' cloudbuild.yaml)", + "Bash(node *)" + ] + } +} diff --git a/Dockerfile b/Dockerfile index 5a1d71c..be00195 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,16 +34,17 @@ ARG NEXT_PUBLIC_ALLOWED_ORIGINS ARG NEXT_PUBLIC_EXPLORER_L1_URL ARG NEXT_PUBLIC_EXPLORER_L2_URL ARG NEXT_PUBLIC_EXPLORER_NFT_URL +ARG NEXT_PUBLIC_LAYERSWAP_BASE_URL ARG RECAPTCHA_API_KEY ARG NEXT_PUBLIC_RECAPTCHA_SITE_KEY ARG NEXT_PUBLIC_NFT_IMAGE_REPOSITORY ARG NEXT_PUBLIC_NFT_MARKETPLACE_URL ARG NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER -ARG NEXT_PUBLIC_GA_MEASUREMENT_ID -ARG NEXT_PUBLIC_MS_CLARITY_ID -ARG NEXT_PUBLIC_NETWORK -ARG NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT -ARG NEXT_PUBLIC_SECURITY_PIN_LENGTH +ARG NEXT_PUBLIC_GA_MEASUREMENT_ID +ARG NEXT_PUBLIC_MS_CLARITY_ID +ARG NEXT_PUBLIC_NETWORK +ARG NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT +ARG NEXT_PUBLIC_SECURITY_PIN_LENGTH # environment variables ENV NODE_ENV production @@ -70,16 +71,17 @@ ENV NEXT_PUBLIC_ALLOWED_ORIGINS $NEXT_PUBLIC_ALLOWED_ORIGINS ENV NEXT_PUBLIC_EXPLORER_L1_URL $NEXT_PUBLIC_EXPLORER_L1_URL ENV NEXT_PUBLIC_EXPLORER_L2_URL $NEXT_PUBLIC_EXPLORER_L2_URL ENV NEXT_PUBLIC_EXPLORER_NFT_URL $NEXT_PUBLIC_EXPLORER_NFT_URL +ENV NEXT_PUBLIC_LAYERSWAP_BASE_URL $NEXT_PUBLIC_LAYERSWAP_BASE_URL ENV RECAPTCHA_API_KEY $RECAPTCHA_API_KEY ENV NEXT_PUBLIC_RECAPTCHA_SITE_KEY $NEXT_PUBLIC_RECAPTCHA_SITE_KEY ENV NEXT_PUBLIC_NFT_IMAGE_REPOSITORY $NEXT_PUBLIC_NFT_IMAGE_REPOSITORY ENV NEXT_PUBLIC_NFT_MARKETPLACE_URL $NEXT_PUBLIC_NFT_MARKETPLACE_URL ENV NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER $NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER -ENV NEXT_PUBLIC_GA_MEASUREMENT_ID $NEXT_PUBLIC_GA_MEASUREMENT_ID -ENV NEXT_PUBLIC_MS_CLARITY_ID $NEXT_PUBLIC_MS_CLARITY_ID -ENV NEXT_PUBLIC_NETWORK $NEXT_PUBLIC_NETWORK -ENV NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT $NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT -ENV NEXT_PUBLIC_SECURITY_PIN_LENGTH $NEXT_PUBLIC_SECURITY_PIN_LENGTH +ENV NEXT_PUBLIC_GA_MEASUREMENT_ID $NEXT_PUBLIC_GA_MEASUREMENT_ID +ENV NEXT_PUBLIC_MS_CLARITY_ID $NEXT_PUBLIC_MS_CLARITY_ID +ENV NEXT_PUBLIC_NETWORK $NEXT_PUBLIC_NETWORK +ENV NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT $NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT +ENV NEXT_PUBLIC_SECURITY_PIN_LENGTH $NEXT_PUBLIC_SECURITY_PIN_LENGTH # copy dependencies and source COPY --from=deps /app/node_modules ./node_modules diff --git a/cloudbuild.yaml b/cloudbuild.yaml index b99aa2d..32b8c63 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -34,12 +34,13 @@ steps: - NEXT_PUBLIC_EXPLORER_L1_URL=${_NEXT_PUBLIC_EXPLORER_L1_URL} - NEXT_PUBLIC_EXPLORER_L2_URL=${_NEXT_PUBLIC_EXPLORER_L2_URL} - NEXT_PUBLIC_EXPLORER_NFT_URL=${_NEXT_PUBLIC_EXPLORER_NFT_URL} - - NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER=${_NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER} - - NEXT_PUBLIC_GA_MEASUREMENT_ID=${_NEXT_PUBLIC_GA_MEASUREMENT_ID} - - NEXT_PUBLIC_MS_CLARITY_ID=${_NEXT_PUBLIC_MS_CLARITY_ID} - - NEXT_PUBLIC_NETWORK=${_NEXT_PUBLIC_NETWORK} - - NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT=${_NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT} - - NEXT_PUBLIC_SECURITY_PIN_LENGTH=${_NEXT_PUBLIC_SECURITY_PIN_LENGTH} + - NEXT_PUBLIC_LAYERSWAP_BASE_URL=${_NEXT_PUBLIC_LAYERSWAP_BASE_URL} + - NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER=${_NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER} + - NEXT_PUBLIC_GA_MEASUREMENT_ID=${_NEXT_PUBLIC_GA_MEASUREMENT_ID} + - NEXT_PUBLIC_MS_CLARITY_ID=${_NEXT_PUBLIC_MS_CLARITY_ID} + - NEXT_PUBLIC_NETWORK=${_NEXT_PUBLIC_NETWORK} + - NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT=${_NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT} + - NEXT_PUBLIC_SECURITY_PIN_LENGTH=${_NEXT_PUBLIC_SECURITY_PIN_LENGTH} secretEnv: [ 'BACKEND_API_TOKEN_SECRET' ] @@ -82,13 +83,14 @@ steps: '--build-arg', 'NEXT_PUBLIC_EXPLORER_L1_URL=${_NEXT_PUBLIC_EXPLORER_L1_URL}', '--build-arg', 'NEXT_PUBLIC_EXPLORER_L2_URL=${_NEXT_PUBLIC_EXPLORER_L2_URL}', '--build-arg', 'NEXT_PUBLIC_EXPLORER_NFT_URL=${_NEXT_PUBLIC_EXPLORER_NFT_URL}', - '--build-arg', 'NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER=${_NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER}', - '--build-arg', 'NEXT_PUBLIC_GA_MEASUREMENT_ID=${_NEXT_PUBLIC_GA_MEASUREMENT_ID}', - '--build-arg', 'NEXT_PUBLIC_MS_CLARITY_ID=${_NEXT_PUBLIC_MS_CLARITY_ID}', - '--build-arg', 'NEXT_PUBLIC_NETWORK=${_NEXT_PUBLIC_NETWORK}', - '--build-arg', 'NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT=${_NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT}', - '--build-arg', 'NEXT_PUBLIC_SECURITY_PIN_LENGTH=${_NEXT_PUBLIC_SECURITY_PIN_LENGTH}', - '.' + '--build-arg', 'NEXT_PUBLIC_LAYERSWAP_BASE_URL=${_NEXT_PUBLIC_LAYERSWAP_BASE_URL}', + '--build-arg', 'NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER=${_NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER}', + '--build-arg', 'NEXT_PUBLIC_GA_MEASUREMENT_ID=${_NEXT_PUBLIC_GA_MEASUREMENT_ID}', + '--build-arg', 'NEXT_PUBLIC_MS_CLARITY_ID=${_NEXT_PUBLIC_MS_CLARITY_ID}', + '--build-arg', 'NEXT_PUBLIC_NETWORK=${_NEXT_PUBLIC_NETWORK}', + '--build-arg', 'NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT=${_NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT}', + '--build-arg', 'NEXT_PUBLIC_SECURITY_PIN_LENGTH=${_NEXT_PUBLIC_SECURITY_PIN_LENGTH}', + '.' ] secretEnv: [ 'BACKEND_API_TOKEN_SECRET' diff --git a/example_env b/example_env index ef6be6c..e68417d 100644 --- a/example_env +++ b/example_env @@ -33,11 +33,12 @@ NEXT_PUBLIC_NFT_MARKETPLACE_URL='https://testnets.opensea.io/assets/arbitrum_sep NEXT_PUBLIC_EXPLORER_L1_URL='https://sepolia.etherscan.io' NEXT_PUBLIC_EXPLORER_L2_URL='https://sepolia.arbiscan.io/' NEXT_PUBLIC_EXPLORER_NFT_URL='https://sepolia.arbiscan.io' -NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER={chatizalo phone number} -NEXT_PUBLIC_NETWORK='scroll sepolia' -NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT=3 -NEXT_PUBLIC_SECURITY_PIN_LENGTH=6 +NEXT_PUBLIC_LAYERSWAP_BASE_URL='https://testnet.layerswap.io/app' +NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER={chatizalo phone number} +NEXT_PUBLIC_NETWORK='scroll sepolia' +NEXT_PUBLIC_SECURITY_RECOVERY_QUESTIONS_COUNT=3 +NEXT_PUBLIC_SECURITY_PIN_LENGTH=6 # analytics NEXT_PUBLIC_GA_MEASUREMENT_ID={google analytics measurement id} -NEXT_PUBLIC_MS_CLARITY_ID={microsoft clarity id} +NEXT_PUBLIC_MS_CLARITY_ID={microsoft clarity id} diff --git a/next.config.js b/next.config.js index e93be99..10b8b2d 100644 --- a/next.config.js +++ b/next.config.js @@ -14,6 +14,7 @@ module.exports = { } }, output: 'standalone', + images: { domains: [ 'storage.googleapis.com', @@ -88,6 +89,8 @@ module.exports = { use: ['@svgr/webpack'] } ) + config.resolve.fallback = { ...config.resolve.fallback, fs: false, net: false, tls: false } + config.externals.push('pino-pretty', 'lokijs', 'encoding') return config }, // https://nextjs.org/docs/api-reference/next.config.js/headers @@ -130,7 +133,7 @@ module.exports = { img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; - frame-src 'self' https://www.google.com https://www.youtube.com; + frame-src 'self' https://www.google.com https://www.youtube.com https://layerswap.io https://*.layerswap.io; frame-ancestors 'self'; ` .replace(/\s+/g, ' ') diff --git a/package.json b/package.json index 4e3089a..eb23470 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "yup": "^1.3.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.8", + "@biomejs/biome": "2.3.11", "@commitlint/cli": "20.0.0", "@commitlint/config-conventional": "20.0.0", "@svgr/webpack": "^8.1.0", diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh index 7b88fb7..1b77d9a 100644 --- a/scripts/docker-build.sh +++ b/scripts/docker-build.sh @@ -36,6 +36,7 @@ docker build \ --build-arg NEXT_PUBLIC_EXPLORER_L1_URL="$NEXT_PUBLIC_EXPLORER_L1_URL" \ --build-arg NEXT_PUBLIC_EXPLORER_L2_URL="$NEXT_PUBLIC_EXPLORER_L2_URL" \ --build-arg NEXT_PUBLIC_EXPLORER_NFT_URL="$NEXT_PUBLIC_EXPLORER_NFT_URL" \ + --build-arg NEXT_PUBLIC_LAYERSWAP_BASE_URL="$NEXT_PUBLIC_LAYERSWAP_BASE_URL" \ --build-arg NEXT_PUBLIC_ALLOWED_ORIGINS="$NEXT_PUBLIC_ALLOWED_ORIGINS" \ --build-arg NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER="$NEXT_PUBLIC_CHATIZALO_PHONE_NUMBER" \ --build-arg NEXT_PUBLIC_GA_MEASUREMENT_ID="$NEXT_PUBLIC_GA_MEASUREMENT_ID" \ diff --git a/src/app/deposit/header.tsx b/src/app/deposit/header.tsx new file mode 100644 index 0000000..4bf6261 --- /dev/null +++ b/src/app/deposit/header.tsx @@ -0,0 +1,40 @@ +'use client' + +import Box from '@mui/material/Box' +import Stack from '@mui/material/Stack' +import AppBar from '@mui/material/AppBar' +import Toolbar from '@mui/material/Toolbar' + +import { LogoWithName } from 'src/components/logo' + +import { HEADER } from 'src/layouts/config-layout' +import LanguagePopover from 'src/layouts/common/language-popover' +import SettingsModeButton from 'src/layouts/common/settings-mode-button' + +// ---------------------------------------------------------------------- + +export default function DepositHeader() { + return ( + + + + + + + + + + + + + ) +} diff --git a/src/app/deposit/layout.tsx b/src/app/deposit/layout.tsx new file mode 100644 index 0000000..80e1cd7 --- /dev/null +++ b/src/app/deposit/layout.tsx @@ -0,0 +1,29 @@ +import type { ReactNode } from 'react' + +import Box from '@mui/material/Box' + +import BaseLayout from 'src/layouts/baseLayout' + +import DepositHeader from './header' + +// ---------------------------------------------------------------------- + +export default function DepositLayout({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ) +} diff --git a/src/app/deposit/page.tsx b/src/app/deposit/page.tsx new file mode 100644 index 0000000..7da9010 --- /dev/null +++ b/src/app/deposit/page.tsx @@ -0,0 +1,17 @@ +import { Suspense } from 'react' + +import { DepositView } from 'src/sections/deposit/view' + +// ---------------------------------------------------------------------- + +export const metadata = { + title: 'ChatterPay: Deposit' +} + +export default function DepositPage() { + return ( + + + + ) +} diff --git a/src/config-global.ts b/src/config-global.ts index 09eba2a..3ee0fca 100644 --- a/src/config-global.ts +++ b/src/config-global.ts @@ -86,6 +86,9 @@ export const NFT_SHARE = 'https://api.whatsapp.com/send/?text=MESSAGE' export const STORAGE_KEY_TOKEN = `chatterpay_${APP_ENV}_jwtToken` export const STORAGE_KEY_SETTINGS = `chatterpay_${APP_ENV}_settings` export const CONTACT_EMAIL = 'contacto@chatterpay.com.ar' +export const LAYERSWAP_BG = '#0c1526' +export const LAYERSWAP_BASE_URL = + process.env.NEXT_PUBLIC_LAYERSWAP_BASE_URL || 'https://testnet.layerswap.io/app' export const GET_BALANCES_FROM_BACKEND = true export const NOTIFICATIONS_PAGE_SIZE: number = Number.isNaN( parseInt(process.env.NEXT_PUBLIC_NOTIFICATIONS_PAGE_SIZE ?? '', 10) diff --git a/src/layouts/common/notifications-modal.tsx b/src/layouts/common/notifications-modal.tsx index 15467d9..2b6dec9 100644 --- a/src/layouts/common/notifications-modal.tsx +++ b/src/layouts/common/notifications-modal.tsx @@ -257,7 +257,7 @@ export default function NotificationsModal({ open, onClose }: Props) { - + - + {renderMessage(notification.message)} diff --git a/src/locales/langs/br.json b/src/locales/langs/br.json index a480c92..39c0616 100644 --- a/src/locales/langs/br.json +++ b/src/locales/langs/br.json @@ -357,7 +357,9 @@ "network": "Rede", "network-warning": "Qualquer ativo enviado em outra rede será perdido", "wallet-address": "Endereço da carteira", - "copy-address": "Copiar endereço" + "copy-address": "Copiar endereço", + "show-address": "Ver meu endereço na Scroll", + "back-to-deposit": "Voltar ao depósito" }, "balances": { "title": "Seu dinheiro", @@ -661,5 +663,18 @@ "description": "Acesse o ChatterPay diretamente pelo Telegram. Consulte sua carteira e saldo sem sair do chat — rápido, seguro e privado." } } + }, + "layerswapDeposit": { + "title": "Depositar no ChatterPay", + "description": "Transfira seus ativos para Scroll e deposite diretamente na sua carteira ChatterPay.", + "returnButton": "Voltar ao ChatterPay", + "whatsappReturnText": "Quero ver meu saldo", + "actionButton": "Depositar no ChatterPay", + "badge": "Powered by Layerswap", + "errors": { + "title": "Solicitação inválida", + "missingAddress": "Um endereço de carteira é necessário. Forneça seu endereço na URL (?address=0x...).", + "invalidAddress": "O endereço de carteira fornecido não é um endereço Ethereum válido." + } } } diff --git a/src/locales/langs/en.json b/src/locales/langs/en.json index 604facc..1583de1 100644 --- a/src/locales/langs/en.json +++ b/src/locales/langs/en.json @@ -368,7 +368,9 @@ "network": "Network", "network-warning": "Any asset sent in other network will be lost", "wallet-address": "Wallet Address", - "copy-address": "Copy Address" + "copy-address": "Copy Address", + "show-address": "See my address on Scroll", + "back-to-deposit": "Back to deposit" }, "balances": { "title": "Your money", @@ -670,5 +672,18 @@ "description": "Access ChatterPay directly from Telegram. Check your wallet and balance without leaving the chat — fast, secure, and private." } } + }, + "layerswapDeposit": { + "title": "Deposit to ChatterPay", + "description": "Bridge your assets to Scroll and deposit directly into your ChatterPay wallet.", + "returnButton": "Return to ChatterPay", + "whatsappReturnText": "I want to see my balance", + "actionButton": "Deposit to ChatterPay", + "badge": "Powered by Layerswap", + "errors": { + "title": "Invalid Request", + "missingAddress": "A wallet address is required. Please provide your address in the URL (?address=0x...).", + "invalidAddress": "The wallet address provided is not a valid Ethereum address." + } } } diff --git a/src/locales/langs/es.json b/src/locales/langs/es.json index 9f400c6..d516f05 100644 --- a/src/locales/langs/es.json +++ b/src/locales/langs/es.json @@ -356,7 +356,9 @@ "network": "Red", "network-warning": "Cualquier activo enviado en otra red se perderá", "wallet-address": "Dirección de billetera", - "copy-address": "Copiar dirección" + "copy-address": "Copiar dirección", + "show-address": "Ver mi dirección en Scroll", + "back-to-deposit": "Volver a depositar" }, "balances": { "title": "Tu dinero", @@ -660,5 +662,18 @@ "description": "Accedé a ChatterPay directamente desde Telegram. Consultá tu billetera y saldo sin salir del chat: rápido, seguro y privado." } } + }, + "layerswapDeposit": { + "title": "Depositar en ChatterPay", + "description": "Transfiere tus activos a Scroll y deposita directamente en tu billetera ChatterPay.", + "returnButton": "Volver a ChatterPay", + "whatsappReturnText": "Quiero ver mi balance", + "actionButton": "Depositar en ChatterPay", + "badge": "Impulsado por Layerswap", + "errors": { + "title": "Solicitud inválida", + "missingAddress": "Se requiere una dirección de billetera. Proporciónala en la URL (?address=0x...).", + "invalidAddress": "La dirección de billetera proporcionada no es una dirección Ethereum válida." + } } } diff --git a/src/routes/paths.ts b/src/routes/paths.ts index 1126b71..e40fa3f 100644 --- a/src/routes/paths.ts +++ b/src/routes/paths.ts @@ -12,6 +12,7 @@ export const paths = { terms: '/terms', policy: '/policy', aboutUs: '/about-us', + deposit: '/deposit', products: { root: '/products', diff --git a/src/sections/banking/banking-balances.tsx b/src/sections/banking/banking-balances.tsx index fc1df20..5da7e32 100644 --- a/src/sections/banking/banking-balances.tsx +++ b/src/sections/banking/banking-balances.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState } from 'react' import { enqueueSnackbar } from 'notistack' import QRCode from 'react-qr-code' @@ -18,9 +18,6 @@ import { DialogContent, type SelectChangeEvent } from '@mui/material' -import { alpha, useTheme } from '@mui/material/styles' - -import { useRouter } from 'src/routes/hooks' import { useBoolean } from 'src/hooks/use-boolean' import { useResponsive } from 'src/hooks/use-responsive' @@ -32,6 +29,8 @@ import { BOT_WAPP_URL, EXPLORER_L2_URL } from 'src/config-global' import Iconify from 'src/components/iconify' +import LayerswapWidget from 'src/sections/deposit/view/layerswap-widget' + import type { IBalances, CurrencyKey } from 'src/types/wallet' // ---------------------------------------------------------------------- @@ -59,11 +58,15 @@ export default function BankingBalances({ const walletLinkL2 = `${EXPLORER_L2_URL}/address/${tableData?.wallet || ''}` const { t } = useTranslate() - const theme = useTheme() const mdUp = useResponsive('up', 'md') const depositModal = useBoolean() - const router = useRouter() + const [showAddress, setShowAddress] = useState(false) + + const handleCloseDeposit = () => { + depositModal.onFalse() + setShowAddress(false) + } const sendReciveUrl = BOT_WAPP_URL.replaceAll('MESSAGE', t('balances.wapp-msg')) @@ -181,82 +184,120 @@ export default function BankingBalances({ ) + // Deposit modal — uses theme palette so it respects dark/light mode + const renderDepositModal = ( - - + + {t('deposit.title')} - + - - - {/* QR Code */} - - - - - - - {t('deposit.wallet-address')} - - - {tableData?.wallet} - - + + {!showAddress ? ( + + - {/* Network Info */} - + + + ) : ( + + {/* QR Code */} - - {t('deposit.network')}: Scroll + > + + + + + + {t('deposit.wallet-address')} + + + {tableData?.wallet} + - - {/* Warning Alert */} - - {t('deposit.network-warning')} - - - - + {/* Network Info */} + + + + {t('deposit.network')}: Scroll + + + + {/* Warning Alert */} + + {t('deposit.network-warning')} + + + + + + + )} ) diff --git a/src/sections/deposit/view/deposit-view.tsx b/src/sections/deposit/view/deposit-view.tsx new file mode 100644 index 0000000..b0a1fec --- /dev/null +++ b/src/sections/deposit/view/deposit-view.tsx @@ -0,0 +1,117 @@ +'use client' + +import { useMemo } from 'react' +import { useSearchParams } from 'next/navigation' + +import Box from '@mui/material/Box' +import Alert from '@mui/material/Alert' +import Button from '@mui/material/Button' +import Container from '@mui/material/Container' +import AlertTitle from '@mui/material/AlertTitle' +import Typography from '@mui/material/Typography' +import { alpha, useTheme } from '@mui/material/styles' + +import { useTranslate } from 'src/locales' +import { CHATIZALO_PHONE_NUMBER } from 'src/config-global' + +import Iconify from 'src/components/iconify' + +import LayerswapWidget from './layerswap-widget' + +// ---------------------------------------------------------------------- + +const WHATSAPP_BASE_URL = 'https://api.whatsapp.com/send/' + +const isValidEthAddress = (address: string): boolean => /^0x[a-fA-F0-9]{40}$/.test(address) + +// ---------------------------------------------------------------------- + +export default function DepositView() { + const { t } = useTranslate() + const theme = useTheme() + const searchParams = useSearchParams() + + const address = searchParams.get('address') || '' + const hasAddress = address.length > 0 + const isValid = hasAddress && isValidEthAddress(address) + + const primaryMain = theme.palette.primary.main + + const whatsappReturnUrl = useMemo(() => { + const params = new URLSearchParams({ + phone: String(CHATIZALO_PHONE_NUMBER), + text: t('layerswapDeposit.whatsappReturnText', 'I want to see my balance'), + type: 'phone_number', + app_absent: '0' + }) + return `${WHATSAPP_BASE_URL}?${params.toString()}` + }, [t]) + + // ---------------------------------------------------------------------- + + const renderError = () => { + const errorKey = hasAddress + ? 'layerswapDeposit.errors.invalidAddress' + : 'layerswapDeposit.errors.missingAddress' + + const errorFallback = hasAddress + ? 'The wallet address provided is not a valid Ethereum address.' + : 'A wallet address is required. Please provide your address in the URL (?address=0x...).' + + return ( + + + {t('layerswapDeposit.errors.title', 'Invalid Request')} + {t(errorKey, errorFallback)} + + + ) + } + + // ---------------------------------------------------------------------- + + return ( + + + {t('layerswapDeposit.title', 'Deposit to ChatterPay')} + + + {isValid ? : renderError()} + + + + + + ) +} diff --git a/src/sections/deposit/view/index.ts b/src/sections/deposit/view/index.ts new file mode 100644 index 0000000..5ec1436 --- /dev/null +++ b/src/sections/deposit/view/index.ts @@ -0,0 +1 @@ +export { default as DepositView } from './deposit-view' diff --git a/src/sections/deposit/view/layerswap-widget.tsx b/src/sections/deposit/view/layerswap-widget.tsx new file mode 100644 index 0000000..9877fd9 --- /dev/null +++ b/src/sections/deposit/view/layerswap-widget.tsx @@ -0,0 +1,137 @@ +'use client' + +import { useMemo } from 'react' + +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import Typography from '@mui/material/Typography' +import { alpha, useTheme } from '@mui/material/styles' + +import { useTranslate } from 'src/locales' +import { LAYERSWAP_BASE_URL } from 'src/config-global' + +import Iconify from 'src/components/iconify' + +// ---------------------------------------------------------------------- + +type Props = { + destAddress: string +} + +// ---------------------------------------------------------------------- +// Layerswap redirect integration. +// +// We use the redirect/external-link pattern for all platforms (desktop + +// mobile, iOS + Android) because an embedded iframe breaks external-wallet +// deep-links (WalletConnect, MetaMask, etc.) on every platform, not just +// iOS Safari. +// +// URL query parameters reference: +// https://docs.layerswap.io/integration/UI/Configurations +// +// In development / testing environments LAYERSWAP_BASE_URL points at the +// Layerswap sandbox so no real funds are involved. +// ---------------------------------------------------------------------- + +export default function LayerswapWidget({ destAddress }: Props) { + const { t } = useTranslate() + const theme = useTheme() + + const layerswapUrl = useMemo(() => { + const params = new URLSearchParams({ + // Destination config + to: 'SCROLL_MAINNET', + toAsset: 'USDT', + fromAsset: 'USDT', + destAddress, + + // Lock destination (user should only change source) + lockTo: 'true', + lockToAsset: 'true', + hideTo: 'true', + hideAddress: 'true', + hideRefuel: 'true', + + // CTA button text + actionButtonText: t('layerswapDeposit.actionButton', 'Deposit to ChatterPay'), + + // Always use the dark "default" Layerswap theme for a polished look + theme: 'default' + }) + + return `${LAYERSWAP_BASE_URL}?${params.toString()}` + }, [destAddress, t]) + + const isDark = theme.palette.mode === 'dark' + + return ( + + {/* Wallet icon */} + + + + + {/* Description */} + + {t( + 'layerswapDeposit.description', + 'Bridge your assets to Scroll and deposit directly into your ChatterPay wallet.' + )} + + + {/* CTA */} + + + {/* Badge */} + + {t('layerswapDeposit.badge', 'Powered by Layerswap')} + + + ) +} diff --git a/yarn.lock b/yarn.lock index 67154b9..519393e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -882,7 +882,7 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@biomejs/biome@^2.3.8": +"@biomejs/biome@2.3.11": version "2.3.11" resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.11.tgz#a8f3682b3b2c0112e2728f6d51d9c67a6c5521f8" integrity sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==