import { Transition } from '@headlessui/react' import { ArrowDownTrayIcon, ArrowTopRightOnSquareIcon, CheckCircleIcon, ChevronDownIcon, ExclamationCircleIcon, PencilIcon, } from '@heroicons/react/20/solid' import { useWallet } from '@solana/wallet-adapter-react' import mangoStore from '@store/mangoStore' import Decimal from 'decimal.js' import useMangoAccount from 'hooks/useMangoAccount' import useSolBalance from 'hooks/useSolBalance' import { useTranslation } from 'next-i18next' import Image from 'next/image' import { ChangeEvent, ReactNode, useCallback, useEffect, useMemo, useState, } from 'react' import { createSolanaMessage, notify } from 'utils/notifications' import ActionTokenList from '../account/ActionTokenList' import ButtonGroup from '../forms/ButtonGroup' import Input from '../forms/Input' import Label from '../forms/Label' // import ParticlesBackground from '../ParticlesBackground' // import EditNftProfilePic from '../profile/EditNftProfilePic' // import EditProfileForm from '../profile/EditProfileForm' import Button, { LinkButton } from '../shared/Button' import Loading from '../shared/Loading' import MaxAmountButton from '../shared/MaxAmountButton' import SolBalanceWarnings from '../shared/SolBalanceWarnings' import Modal from '../shared/Modal' import NumberFormat from 'react-number-format' import { withValueLimit } from '@components/swap/MarketSwapForm' import useBanksWithBalances from 'hooks/useBanksWithBalances' import BankAmountWithValue from '@components/shared/BankAmountWithValue' import { isMangoError } from 'types' import ColorBlur from '@components/ColorBlur' import useLocalStorageState from 'hooks/useLocalStorageState' import { ACCEPT_TERMS_KEY, MAX_ACCOUNTS } from 'utils/constants' import { ACCOUNT_ACTIONS_NUMBER_FORMAT_CLASSES } from '@components/BorrowForm' import { WalletReadyState } from '@solana/wallet-adapter-base' import Switch from '@components/forms/Switch' import NotificationCookieStore from '@store/notificationCookieStore' import { usePlausible } from 'next-plausible' import { TelemetryEvents } from 'utils/telemetry' import { waitForSlot } from 'utils/network' import Checkbox from '@components/forms/Checkbox' import { handleInputChange } from 'utils/account' import BounceLoader from '@components/shared/BounceLoader' const UserSetupModal = ({ isOpen, onClose, }: { isOpen: boolean onClose: () => void }) => { const { t } = useTranslation(['common', 'onboarding', 'swap']) const { connected, select, wallet, wallets, publicKey, connect } = useWallet() const { mangoAccountAddress, initialLoad: mangoAccountLoading } = useMangoAccount() const telemetry = usePlausible() const [accountName, setAccountName] = useState('') const [loadingNewAccount, setLoadingNewAccount] = useState(false) const [termsAccepted, setTermsAccepted] = useState(false) const [showSetupStep, setShowSetupStep] = useState(0) const [depositToken, setDepositToken] = useState('USDC') const [depositAmount, setDepositAmount] = useState('') const [submitDeposit, setSubmitDeposit] = useState(false) const [sizePercentage, setSizePercentage] = useState('') const [signToNotifications, setSignToNotifications] = useState(true) // const [showEditProfilePic, setShowEditProfilePic] = useState(false) const { maxSolDeposit } = useSolBalance() const banks = useBanksWithBalances('walletBalance') const [, setAcceptTerms] = useLocalStorageState(ACCEPT_TERMS_KEY, '') const [walletsToDisplay, setWalletstoDisplay] = useState<'default' | 'all'>( 'default', ) //used to sign txes const walletContext = useWallet() const setCookie = NotificationCookieStore((s) => s.setCookie) const walletsDisplayed = useMemo(() => { const firstFive = wallets.slice(0, 5) const detectedWallets = wallets.filter( (w) => w.readyState === WalletReadyState.Installed || w.readyState === WalletReadyState.Loadable, ) if (walletsToDisplay === 'default') { return detectedWallets.length > firstFive.length ? detectedWallets : firstFive } else { return wallets } }, [walletsToDisplay, wallets]) // close onboarding if an account already exists after connecting useEffect(() => { if (connected && mangoAccountAddress && showSetupStep === 1) { onClose() } }, [connected, mangoAccountAddress, showSetupStep]) // move to create account after connecting wallet useEffect(() => { if (connected && !mangoAccountAddress && !mangoAccountLoading) { setShowSetupStep(2) } }, [connected, mangoAccountAddress, mangoAccountLoading]) // move to fund account after creating an account useEffect(() => { if (mangoAccountAddress && showSetupStep === 2) { setShowSetupStep(3) } }, [mangoAccountAddress, showSetupStep]) const handleCreateAccount = useCallback(async () => { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions const connection = mangoStore.getState().connection if (!group || !publicKey) return setLoadingNewAccount(true) try { const { signature: tx, slot } = await client.createMangoAccount( group, 0, accountName || 'Account 1', parseInt(MAX_ACCOUNTS.tokenAccounts), // tokens parseInt(MAX_ACCOUNTS.spotOpenOrders), // serum3 parseInt(MAX_ACCOUNTS.perpAccounts), // perps parseInt(MAX_ACCOUNTS.perpOpenOrders), // perp Oo ) if (tx) { if (signToNotifications) { createSolanaMessage(walletContext, setCookie) } await waitForSlot(connection, slot!) await actions.fetchMangoAccounts(publicKey) await actions.fetchWalletTokens(publicKey) // need to update sol balance after account rent telemetry('accountCreate', { props: { accountNum: 0, enableNotifications: signToNotifications, }, }) notify({ title: t('new-account-success'), type: 'success', txid: tx, }) } } catch (e) { if (isMangoError(e)) { notify({ title: t('new-account-failed'), txid: e?.txid, type: 'error', }) } console.error(e) } finally { setLoadingNewAccount(false) } }, [accountName, publicKey, signToNotifications, t]) const handleDeposit = useCallback(async () => { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions const mangoAccount = mangoStore.getState().mangoAccount.current const bank = group?.banksMapByName.get(depositToken)?.[0] if (!mangoAccount || !group || !bank || !publicKey) return try { setSubmitDeposit(true) const { signature: tx, slot } = await client.tokenDeposit( group, mangoAccount, bank.mint, parseFloat(depositAmount), ) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, }) await actions.reloadMangoAccount(slot) actions.fetchWalletTokens(publicKey) setSubmitDeposit(false) onClose() // setShowSetupStep(4) } catch (e) { setSubmitDeposit(false) console.error(e) if (!isMangoError(e)) return notify({ title: 'Transaction failed', description: e.message, txid: e?.txid, type: 'error', }) } }, [depositAmount, depositToken, onClose, publicKey]) const depositBank = useMemo(() => { return banks.find((b) => b.bank.name === depositToken)?.bank }, [depositToken, banks]) const tokenMax = useMemo(() => { const bank = banks.find((b) => b.bank.name === depositToken) if (bank) { return { amount: bank.walletBalance, decimals: bank.bank.mintDecimals } } return { amount: 0, decimals: 0 } }, [banks, depositToken]) const showInsufficientBalance = tokenMax.amount < Number(depositAmount) || (depositToken === 'SOL' && maxSolDeposit <= 0) const setMax = useCallback(() => { const max = new Decimal(tokenMax.amount).toDecimalPlaces( tokenMax.decimals, Decimal.ROUND_FLOOR, ) setDepositAmount(max.toString()) setSizePercentage('100') }, [tokenMax]) const handleSizePercentage = useCallback( (percentage: string) => { setSizePercentage(percentage) const amount = new Decimal(tokenMax.amount) .mul(percentage) .div(100) .toDecimalPlaces(tokenMax.decimals, Decimal.ROUND_FLOOR) setDepositAmount(amount.toString()) }, [tokenMax], ) const handleNextStep = () => { if (showSetupStep === 0) { setAcceptTerms(Date.now()) } setShowSetupStep(showSetupStep + 1) } return (
next
{connected && mangoAccountLoading ? (
) : ( <> {showSetupStep === 0 ? (

{t('onboarding:intro-heading')}

{t('onboarding:intro-desc')}

{/*
Rewards

Trade. Win. Repeat.

Win amazing prizes every week. Create your account and start trading to earn rewards.

*/}
setTermsAccepted(e.target.checked)} >

{t('accept-terms-desc')} {t('terms-of-use')} and {t('risks')}

) : null} {showSetupStep === 1 ? (

{t('onboarding:connect-wallet')}

{t('onboarding:choose-wallet')}

{walletsDisplayed?.map((w) => ( ))}
{walletsToDisplay !== 'all' ? ( ) : null}
) : null} {showSetupStep === 2 ? (

{t('onboarding:create-account')}

{t('insufficient-sol')}

{t('enable-notifications')}

{t('asked-sign-transaction')}

setSignToNotifications(checked)} />
{t('onboarding:skip')}
) : null} {showSetupStep === 3 ? (

{t('onboarding:fund-account')}

{depositToken ? ( <>
handleInputChange( values, source, setDepositAmount, setSizePercentage, ) } isAllowed={withValueLimit} />
handleSizePercentage(p)} values={['10', '25', '50', '75', '100']} unit="%" />
{depositBank ? (

{t('deposit-amount')}

{/* {depositAmount ? ( <> {' '} ( ) ) : ( <> 0{' '} ($0.00) )} */}

) : null} {t('onboarding:skip')} ) : (

{t('token')}

{t('deposit-rate')}

{t('max')}

)}
) : null}
)}
{/* */} next
) } export default UserSetupModal const CheckBullet = ({ text }: { text: string }) => { return (

{text}

) } const UserSetupTransition = ({ show, children, delay = false, }: { show: boolean children: ReactNode delay?: boolean }) => { return ( {children} ) }