Merge pull request #4 from blockworks-foundation/fix-onboarding

fix account loading and onboarding
This commit is contained in:
tjshipe 2022-08-15 19:10:35 -04:00 committed by GitHub
commit 8e3259b118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 205 additions and 61 deletions

View File

@ -13,10 +13,13 @@ import ConnectedMenu from './wallet/ConnectedMenu'
import WalletIcon from './icons/WalletIcon'
import BounceLoader from './shared/BounceLoader'
import MangoAccountsList from './MangoAccountsList'
import { useWallet } from '@solana/wallet-adapter-react'
import CreateAccountModal from './modals/CreateAccountModal'
export const IS_ONBOARDED_KEY = 'isOnboarded'
const Layout = ({ children }: { children: ReactNode }) => {
const { connected } = useWallet()
const actions = mangoStore((s) => s.actions)
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
const loadingMangoAccount = mangoStore((s) => s.mangoAccount.loading)
@ -26,6 +29,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
const isMobile = width ? width < breakpoints.md : false
const [isOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
const [showUserSetupModal, setShowUserSetupModal] = useState(false)
const [showFirstAccountModal, setShowFirstAccountModal] = useState(false)
useEffect(() => {
if (mangoAccount) {
@ -46,6 +50,12 @@ const Layout = ({ children }: { children: ReactNode }) => {
}
}, [width])
useEffect(() => {
if (connected && isOnboarded && !loadingMangoAccount && !mangoAccount) {
setShowFirstAccountModal(true)
}
}, [connected, isOnboarded, loadingMangoAccount, mangoAccount])
const handleCloseModal = useCallback(() => {
setShowUserSetupModal(false)
}, [])
@ -63,7 +73,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
return (
<>
{loadingMangoAccount && isOnboarded ? (
{connected && loadingMangoAccount && isOnboarded ? (
<div className="fixed z-30 flex h-screen w-full items-center justify-center bg-[rgba(0,0,0,0.7)]">
<BounceLoader />
</div>
@ -146,6 +156,13 @@ const Layout = ({ children }: { children: ReactNode }) => {
onClose={handleCloseModal}
/>
) : null}
{showFirstAccountModal ? (
<CreateAccountModal
isOpen={showFirstAccountModal}
onClose={() => setShowFirstAccountModal(false)}
isFirstAccount
/>
) : null}
</>
)
}

View File

@ -8,7 +8,7 @@ import { Popover, Transition } from '@headlessui/react'
import { MangoAccount } from '@blockworks-foundation/mango-v4'
import mangoStore from '../store/state'
import { LinkButton } from './shared/Button'
import CreateNewAccountModal from './modals/CreateNewAccountModal'
import CreateAccountModal from './modals/CreateAccountModal'
const handleSelectMangoAccount = async (acc: MangoAccount) => {
const set = mangoStore.getState().set
@ -91,7 +91,7 @@ const MangoAccountsList = ({
)}
</Popover>
{showNewAccountModal ? (
<CreateNewAccountModal
<CreateAccountModal
isOpen={showNewAccountModal}
onClose={() => setShowNewAccountModal(false)}
/>

View File

@ -10,8 +10,9 @@ import BounceLoader from '../shared/BounceLoader'
const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
const { t } = useTranslation('common')
const { disconnect } = useWallet()
const { wallet } = useWallet()
const [loading, setLoading] = useState(false)
const set = mangoStore((s) => s.set)
const handleCloseMangoAccount = async () => {
const client = mangoStore.getState().client
@ -22,7 +23,6 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
try {
const tx = await client.closeMangoAccount(group, mangoAccount)
if (tx) {
disconnect()
setLoading(false)
onClose()
notify({
@ -30,6 +30,11 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
type: 'success',
txid: tx,
})
set((state) => {
state.mangoAccount.loading = true
state.mangoAccount.current = undefined
})
setTimeout(() => wallet?.adapter?.disconnect(), 1000)
}
} catch (e) {
setLoading(false)

View File

@ -1,5 +1,4 @@
import { ChangeEvent, useState } from 'react'
import { MangoAccount } from '@blockworks-foundation/mango-v4'
import { useTranslation } from 'next-i18next'
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
@ -11,6 +10,8 @@ import Input from '../forms/Input'
import Label from '../forms/Label'
import { useWallet } from '@solana/wallet-adapter-react'
import { Wallet } from '@project-serum/anchor'
import InlineNotification from '../shared/InlineNotification'
import { MangoAccount } from '@blockworks-foundation/mango-v4'
const getNextAccountNumber = (accounts: MangoAccount[]): number => {
if (accounts.length > 1) {
@ -25,13 +26,22 @@ const getNextAccountNumber = (accounts: MangoAccount[]): number => {
return 0
}
const CreateNewAccountModal = ({ isOpen, onClose }: ModalProps) => {
interface CreateAccountModalProps {
isFirstAccount?: boolean
}
type ModalCombinedProps = ModalProps & CreateAccountModalProps
const CreateAccountModal = ({
isFirstAccount,
isOpen,
onClose,
}: ModalCombinedProps) => {
const { t } = useTranslation('common')
const [loading, setLoading] = useState(false)
const [name, setName] = useState('')
const { wallet } = useWallet()
// This doesn't work yet...
const handleNewAccount = async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
@ -41,7 +51,15 @@ const CreateNewAccountModal = ({ isOpen, onClose }: ModalProps) => {
setLoading(true)
try {
const newAccountNum = getNextAccountNumber(mangoAccounts)
const tx = await client.createMangoAccount(group, newAccountNum, name)
const tx = await client.createMangoAccount(
group,
newAccountNum,
name || `Account ${newAccountNum + 1}`
)
actions.fetchMangoAccount(
wallet!.adapter as unknown as Wallet,
newAccountNum
)
actions.fetchMangoAccounts(wallet!.adapter as unknown as Wallet)
if (tx) {
setLoading(false)
@ -65,28 +83,40 @@ const CreateNewAccountModal = ({ isOpen, onClose }: ModalProps) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="h-64">
<div className="h-80">
{loading ? (
<BounceLoader loadingMessage={t('creating-account')} />
) : (
<div className="flex h-full flex-col justify-between">
<div className="pb-4">
<h2 className="mb-4">{t('new-account')}</h2>
<Label optional text={t('account-name')} />
<h2>{t('create-account')}</h2>
{isFirstAccount ? (
<p className="mt-1">You need a Mango Account to get started.</p>
) : null}
<div className="pt-4">
<Label optional text={t('account-name')} />
</div>
<Input
type="text"
name="name"
id="name"
placeholder="Account 1"
placeholder="Account"
value={name}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setName(e.target.value)
}
/>
</div>
<Button className="w-full" onClick={handleNewAccount} size="large">
{t('create-account')}
</Button>
<div className="space-y-6">
<InlineNotification type="info" desc={t('insufficient-sol')} />
<Button
className="w-full"
onClick={handleNewAccount}
size="large"
>
{t('create-account')}
</Button>
</div>
</div>
)}
</div>
@ -94,4 +124,4 @@ const CreateNewAccountModal = ({ isOpen, onClose }: ModalProps) => {
)
}
export default CreateNewAccountModal
export default CreateAccountModal

View File

@ -20,12 +20,15 @@ import { EnterRightExitLeft, FadeInFadeOut } from '../shared/Transitions'
import Image from 'next/image'
import BounceLoader from '../shared/BounceLoader'
import { notify } from '../../utils/notifications'
import { Wallet } from '@project-serum/anchor'
const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
const { t } = useTranslation()
const { select, wallet, wallets } = useWallet()
const { connected, select, wallet, wallets } = useWallet()
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.loading)
const [accountName, setAccountName] = useState('')
const [loadingAccount, setLoadingAccount] = useState(false)
const [profileName, setProfileName] = useState('')
const [profileCategory, setProfileCategory] = useState('')
const [showSetupStep, setShowSetupStep] = useState(0)
@ -56,11 +59,39 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
}
}
useEffect(() => {
if (mangoAccount) {
setShowSetupStep(3)
const handleCreateAccount = async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
if (!group || !wallet) return
setLoadingAccount(true)
try {
const tx = await client.createMangoAccount(
group,
0,
accountName || 'Account 1'
)
actions.fetchMangoAccount(wallet!.adapter as unknown as Wallet)
// actions.fetchMangoAccounts(wallet!.adapter as unknown as Wallet)
if (tx) {
setLoadingAccount(false)
setShowSetupStep(4)
notify({
title: t('new-account-success'),
type: 'success',
txid: tx,
})
}
} catch (e: any) {
setLoadingAccount(false)
notify({
title: t('new-account-failed'),
txid: e?.signature,
type: 'error',
})
console.log(e)
}
}, [mangoAccount])
}
const handleDeposit = async () => {
const client = mangoStore.getState().client
@ -100,12 +131,25 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
}
}
useEffect(() => {
if (mangoAccount && showSetupStep === 1) {
setIsOnboarded(true)
onClose()
}
}, [mangoAccount, showSetupStep])
useEffect(() => {
if (connected && !mangoAccountLoading) {
setShowSetupStep(2)
}
}, [connected, mangoAccountLoading])
return (
<Modal isOpen={isOpen} onClose={onClose} hideClose>
<div className="absolute top-0 left-0 flex h-0.5 w-full flex-grow bg-th-bkg-4">
<div
style={{
width: `${(showSetupStep / 3) * 100}%`,
width: `${(showSetupStep / 4) * 100}%`,
}}
className="default-transition flex rounded bg-th-primary"
></div>
@ -163,7 +207,7 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
show={showSetupStep === 1}
style={{ height: 'calc(100% - 12px)' }}
>
{mangoAccountLoading ? (
{connected && mangoAccountLoading ? (
<div className="flex h-full items-center justify-center">
<BounceLoader />
</div>
@ -172,10 +216,6 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
<div>
<div className="mb-4">
<h2 className="mb-1">Connect Wallet</h2>
<p>
If you don&apos;t have a Mango Account yet, we&apos;ll
create one for you.
</p>
</div>
<p className="mb-2 font-bold">Choose Wallet</p>
<div className="thin-scroll grid max-h-56 grid-flow-row grid-cols-3 gap-2 overflow-y-auto">
@ -203,16 +243,9 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
))}
</div>
</div>
<div>
<InlineNotification type="info" desc={t('insufficient-sol')} />
<Button
className="mt-4 w-full"
onClick={connectWallet}
size="large"
>
Connect Wallet
</Button>
</div>
<Button className="w-full" onClick={connectWallet} size="large">
Connect Wallet
</Button>
</div>
)}
</EnterRightExitLeft>
@ -269,15 +302,58 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
show={showSetupStep === 3}
style={{ height: 'calc(100% - 12px)' }}
>
{submitDeposit ? (
{loadingAccount ? (
<div className="flex h-full items-center justify-center">
<BounceLoader />
<BounceLoader loadingMessage="Creating Account..." />
</div>
) : (
<div className="flex h-full flex-col justify-between">
<div>
<div className="pb-4">
<h2 className="mb-1">Create Account</h2>
<p>You need a Mango Account to get started.</p>
</div>
<div className="pb-4">
<Label text="Account Name" optional />
<Input
type="text"
name="name"
id="name"
placeholder="Account"
value={accountName}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setAccountName(e.target.value)
}
/>
</div>
</div>
<div className="space-y-6">
<InlineNotification type="info" desc={t('insufficient-sol')} />
<Button
className="mb-4 w-full"
onClick={() => handleCreateAccount()}
size="large"
>
Create Account
</Button>
</div>
</div>
)}
</EnterRightExitLeft>
<EnterRightExitLeft
className="absolute top-0.5 left-0 z-20 w-full bg-th-bkg-1 p-6"
show={showSetupStep === 4}
style={{ height: 'calc(100% - 12px)' }}
>
{submitDeposit ? (
<div className="flex h-full items-center justify-center">
<BounceLoader loadingMessage="Funding your account..." />
</div>
) : (
<div className="flex h-full flex-col justify-between">
<div className="relative">
<h2 className="mb-4">Fund Your Account</h2>
<FadeInFadeOut className="relative" show={!!depositToken}>
<FadeInFadeOut show={!!depositToken}>
<Label text="Amount" />
<div className="grid grid-cols-2">
<button
@ -313,7 +389,7 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
</div>
</FadeInFadeOut>
{!depositToken ? (
<div className="absolute top-16 mt-2">
<div className="absolute top-10 mt-2 h-56 w-full overflow-auto">
<DepositTokenList onSelect={setDepositToken} />
</div>
) : null}

View File

@ -16,7 +16,7 @@ const DepositTokenList = ({ onSelect }: { onSelect: (x: any) => void }) => {
<p className="text-xs">{t('token')}</p>
</div>
<div className="w-1/4 text-right">
<p className="text-xs">{t('rate')}</p>
<p className="text-xs">{t('deposit-rate')}</p>
</div>
<div className="w-2/4 text-right">
<p className="whitespace-nowrap text-xs">{t('wallet-balance')}</p>

View File

@ -21,10 +21,11 @@ const ConnectedMenu = () => {
const isMobile = width ? width < breakpoints.sm : false
const handleDisconnect = useCallback(() => {
wallet?.adapter?.disconnect()
set((state) => {
state.mangoAccount.loading = true
state.mangoAccount.current = undefined
})
setTimeout(() => wallet?.adapter?.disconnect(), 500)
notify({
type: 'info',
title: t('wallet-disconnected'),

View File

@ -5,14 +5,22 @@ import { Wallet } from '@project-serum/anchor'
const WalletListener = () => {
const { wallet, connected, disconnecting } = useWallet()
const mangoAccounts = mangoStore((s) => s.mangoAccounts)
useEffect(() => {
const actions = mangoStore.getState().actions
const onConnect = async () => {
if (!wallet) return
await actions.fetchMangoAccount(wallet.adapter as unknown as Wallet)
actions.fetchMangoAccounts(wallet.adapter as unknown as Wallet)
await actions.fetchMangoAccounts(wallet.adapter as unknown as Wallet)
if (mangoAccounts.length) {
actions.fetchMangoAccount(
wallet.adapter as unknown as Wallet,
mangoAccounts[0].accountNum
)
} else {
actions.fetchMangoAccount(wallet.adapter as unknown as Wallet)
}
actions.fetchProfilePicture(wallet.adapter as unknown as Wallet)
actions.fetchWalletTokens(wallet.adapter as unknown as Wallet)
}
@ -21,11 +29,10 @@ const WalletListener = () => {
if (connected) {
onConnect()
}
}, [wallet, connected])
}, [wallet, connected, mangoAccounts.length])
useEffect(() => {
const setStore = mangoStore.getState().set
if (disconnecting) {
setStore((state) => {
state.mangoAccount.current = undefined

View File

@ -209,7 +209,7 @@ const Index: NextPage = () => {
) : null
) : (
<SheenLoader>
<div className="h-[88px] w-[180px] rounded-md bg-th-bkg-2" />
<div className="h-[72px] w-[180px] rounded-md bg-th-bkg-2" />
</SheenLoader>
)}
</div>

View File

@ -23,10 +23,12 @@
"connect": "Connect",
"connect-helper": "Connect to get started",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Value",
"daily-volume": "24h Volume",
"date": "Date",
"deposit": "Deposit",
"deposit-rate": "Deposit Rate (APR)",
"deposit-value": "Deposit Value",
"disconnect": "Disconnect",
"edit-account": "Edit Account",
@ -38,6 +40,7 @@
"governance": "Governance",
"health": "Health",
"health-impact": "Health Impact",
"insufficient-sol": "The Solana blockchain requires 0.00757 SOL to create a Mango Account. This covers rent for your account and will be refunded if you close your account.",
"interest-earned": "Interest Earned",
"language": "Language",
"learn": "Learn",

View File

@ -23,10 +23,12 @@
"connect": "Connect",
"connect-helper": "Connect to get started",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Value",
"daily-volume": "24h Volume",
"date": "Date",
"deposit": "Deposit",
"deposit-rate": "Deposit Rate (APR)",
"deposit-value": "Deposit Value",
"disconnect": "Disconnect",
"edit-account": "Edit Account",
@ -38,6 +40,7 @@
"governance": "Governance",
"health": "Health",
"health-impact": "Health Impact",
"insufficient-sol": "The Solana blockchain requires 0.00757 SOL to create a Mango Account. This covers rent for your account and will be refunded if you close your account.",
"interest-earned": "Interest Earned",
"language": "Language",
"learn": "Learn",

View File

@ -23,10 +23,12 @@
"connect": "Connect",
"connect-helper": "Connect to get started",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Value",
"daily-volume": "24h Volume",
"date": "Date",
"deposit": "Deposit",
"deposit-rate": "Deposit Rate (APR)",
"deposit-value": "Deposit Value",
"disconnect": "Disconnect",
"edit-account": "Edit Account",
@ -38,6 +40,7 @@
"governance": "Governance",
"health": "Health",
"health-impact": "Health Impact",
"insufficient-sol": "The Solana blockchain requires 0.00757 SOL to create a Mango Account. This covers rent for your account and will be refunded if you close your account.",
"interest-earned": "Interest Earned",
"language": "Language",
"learn": "Learn",

View File

@ -23,10 +23,12 @@
"connect": "Connect",
"connect-helper": "Connect to get started",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Value",
"daily-volume": "24h Volume",
"date": "Date",
"deposit": "Deposit",
"deposit-rate": "Deposit Rate (APR)",
"deposit-value": "Deposit Value",
"disconnect": "Disconnect",
"edit-account": "Edit Account",
@ -38,6 +40,7 @@
"governance": "Governance",
"health": "Health",
"health-impact": "Health Impact",
"insufficient-sol": "The Solana blockchain requires 0.00757 SOL to create a Mango Account. This covers rent for your account and will be refunded if you close your account.",
"interest-earned": "Interest Earned",
"language": "Language",
"learn": "Learn",

View File

@ -134,7 +134,7 @@ export type MangoStore = {
) => Promise<void>
fetchCoingeckoPrices: () => Promise<void>
fetchGroup: () => Promise<void>
fetchMangoAccount: (wallet: Wallet) => Promise<void>
fetchMangoAccount: (wallet: Wallet, accountNumber?: number) => Promise<void>
fetchMangoAccounts: (wallet: Wallet) => Promise<void>
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
fetchProfilePicture: (wallet: Wallet) => void
@ -161,7 +161,7 @@ const mangoStore = create<MangoStore>(
jupiterTokens: [],
mangoAccount: {
current: undefined,
loading: false,
loading: true,
stats: {
interestTotals: { data: [], loading: false },
performance: { data: [], loading: false },
@ -315,7 +315,7 @@ const mangoStore = create<MangoStore>(
console.error('Error fetching group', e)
}
},
fetchMangoAccount: async (wallet) => {
fetchMangoAccount: async (wallet, accountNumber) => {
const set = get().set
try {
const group = get().group
@ -340,15 +340,10 @@ const mangoStore = create<MangoStore>(
}
)
set((state) => {
state.mangoAccount.loading = true
})
const mangoAccount = await client.getOrCreateMangoAccount(
const mangoAccount = await client.getMangoAccountForOwner(
group,
wallet.publicKey,
0,
'Account'
accountNumber || 0
)
// let orders = await client.getSerum3Orders(
@ -356,8 +351,9 @@ const mangoStore = create<MangoStore>(
// SERUM3_PROGRAM_ID['devnet'],
// 'BTC/USDC'
// )
await mangoAccount.reloadAccountData(client, group)
if (mangoAccount) {
await mangoAccount.reloadAccountData(client, group)
}
set((state) => {
state.client = client
state.mangoAccount.current = mangoAccount