mango-ui-v3/pages/account.tsx

577 lines
20 KiB
TypeScript
Raw Normal View History

2022-03-01 19:30:25 -08:00
import React, {
Fragment,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import {
2022-01-18 16:06:27 -08:00
BellIcon,
CurrencyDollarIcon,
2022-03-01 11:06:02 -08:00
DuplicateIcon,
2021-12-07 20:00:02 -08:00
ExclamationCircleIcon,
2022-03-01 11:06:02 -08:00
GiftIcon,
LinkIcon,
2021-07-23 07:07:05 -07:00
PencilIcon,
2022-03-01 19:30:25 -08:00
SwitchHorizontalIcon,
2021-11-08 08:42:55 -08:00
TrashIcon,
2022-02-09 07:11:51 -08:00
UsersIcon,
} from '@heroicons/react/outline'
2022-03-01 19:30:25 -08:00
import { ChevronDownIcon } from '@heroicons/react/solid'
2022-03-01 11:06:02 -08:00
import { nativeToUi, ZERO_BN } from '@blockworks-foundation/mango-client'
2022-03-22 02:18:58 -07:00
import useMangoStore, { serumProgramId, MNGO_INDEX } from 'stores/useMangoStore'
import PageBodyContainer from 'components/PageBodyContainer'
import TopBar from 'components/TopBar'
import AccountOrders from 'components/account_page/AccountOrders'
import AccountHistory from 'components/account_page/AccountHistory'
import AccountsModal from 'components/AccountsModal'
import AccountOverview from 'components/account_page/AccountOverview'
import AccountInterest from 'components/account_page/AccountInterest'
import AccountFunding from 'components/account_page/AccountFunding'
2022-04-20 14:05:55 -07:00
import AccountPerformancePerToken from 'components/account_page/AccountPerformancePerToken'
2022-03-22 02:18:58 -07:00
import AccountNameModal from 'components/AccountNameModal'
import { IconButton, LinkButton } from 'components/Button'
import EmptyState from 'components/EmptyState'
import Loading from 'components/Loading'
import Swipeable from 'components/mobile/Swipeable'
import Tabs from 'components/Tabs'
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'components/TradePageGrid'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'
import { PublicKey } from '@solana/web3.js'
2022-03-22 02:18:58 -07:00
import CloseAccountModal from 'components/CloseAccountModal'
import { notify } from 'utils/notifications'
2022-01-16 15:16:47 -08:00
import {
actionsSelector,
mangoAccountSelector,
mangoGroupSelector,
2022-03-22 02:18:58 -07:00
} from 'stores/selectors'
import CreateAlertModal from 'components/CreateAlertModal'
import { copyToClipboard } from 'utils'
import DelegateModal from 'components/DelegateModal'
2022-03-01 19:30:25 -08:00
import { Menu, Transition } from '@headlessui/react'
2022-03-22 02:18:58 -07:00
import { useWallet } from '@solana/wallet-adapter-react'
import { handleWalletConnect } from 'components/ConnectWalletButton'
2022-03-25 02:48:51 -07:00
import { MangoAccountLookup } from 'components/account_page/MangoAccountLookup'
2022-05-10 20:29:35 -07:00
import NftProfilePicModal from 'components/NftProfilePicModal'
2022-05-15 18:24:48 -07:00
import ProfileImage from 'components/ProfileImage'
export async function getStaticProps({ locale }) {
return {
props: {
2022-02-09 07:11:51 -08:00
...(await serverSideTranslations(locale, [
'common',
'close-account',
'delegate',
'alerts',
2022-05-03 18:53:35 -07:00
'account-performance',
2022-04-20 11:09:31 -07:00
'share-modal',
2022-05-10 20:29:35 -07:00
'profile',
2022-02-09 07:11:51 -08:00
])),
// Will be passed to the page component as props
},
}
}
2022-04-25 09:53:49 -07:00
const TABS = [
'Portfolio',
'Orders',
'History',
'Interest',
'Funding',
'Performance',
]
export default function Account() {
2022-02-09 07:11:51 -08:00
const { t } = useTranslation(['common', 'close-account', 'delegate'])
2022-03-01 11:06:02 -08:00
const { width } = useViewport()
const router = useRouter()
2022-03-22 02:18:58 -07:00
const { connected, wallet, publicKey } = useWallet()
const isLoading = useMangoStore((s) => s.selectedMangoAccount.initialLoad)
2022-01-16 15:16:47 -08:00
const mangoAccount = useMangoStore(mangoAccountSelector)
const mangoGroup = useMangoStore(mangoGroupSelector)
const actions = useMangoStore(actionsSelector)
const setMangoStore = useMangoStore((s) => s.set)
2022-03-01 11:06:02 -08:00
const [showAccountsModal, setShowAccountsModal] = useState(false)
const [showNameModal, setShowNameModal] = useState(false)
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
const [showAlertsModal, setShowAlertsModal] = useState(false)
const [showDelegateModal, setShowDelegateModal] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const [resetOnLeave, setResetOnLeave] = useState(false)
const [mngoAccrued, setMngoAccrued] = useState(ZERO_BN)
const [viewIndex, setViewIndex] = useState(0)
const [activeTab, setActiveTab] = useState(TABS[0])
2022-05-10 20:29:35 -07:00
const [showProfilePicModal, setShowProfilePicModal] = useState(false)
const loadingTransaction = useMangoStore(
(s) => s.wallet.nfts.loadingTransaction
)
2022-03-01 11:06:02 -08:00
2022-03-22 02:18:58 -07:00
const connecting = wallet?.adapter?.connecting
const isMobile = width ? width < breakpoints.sm : false
const { pubkey } = router.query
2022-03-22 02:18:58 -07:00
const isDelegatedAccount = publicKey
? !mangoAccount?.owner?.equals(publicKey)
: false
2022-01-18 16:06:27 -08:00
const handleCloseAlertModal = useCallback(() => {
setShowAlertsModal(false)
}, [])
const handleCloseAccounts = useCallback(() => {
setShowAccountsModal(false)
}, [])
2021-07-23 07:07:05 -07:00
const handleCloseNameModal = useCallback(() => {
setShowNameModal(false)
}, [])
2022-01-18 16:06:27 -08:00
2021-11-08 08:42:55 -08:00
const handleCloseCloseAccountModal = useCallback(() => {
setShowCloseAccountModal(false)
}, [])
2021-07-23 07:07:05 -07:00
2022-02-09 07:11:51 -08:00
const handleCloseDelegateModal = useCallback(() => {
setShowDelegateModal(false)
}, [])
2022-05-10 20:29:35 -07:00
const handleCloseProfilePicModal = useCallback(() => {
setShowProfilePicModal(false)
}, [])
2022-03-22 02:18:58 -07:00
const handleConnect = useCallback(() => {
2022-03-29 06:02:29 -07:00
if (wallet) {
handleWalletConnect(wallet)
}
2022-03-22 02:18:58 -07:00
}, [wallet])
useEffect(() => {
async function loadUnownedMangoAccount() {
try {
2022-03-29 06:02:29 -07:00
if (!pubkey) {
return
}
const unownedMangoAccountPubkey = new PublicKey(pubkey)
const mangoClient = useMangoStore.getState().connection.client
if (mangoGroup) {
const unOwnedMangoAccount = await mangoClient.getMangoAccount(
unownedMangoAccountPubkey,
serumProgramId
)
setMangoStore((state) => {
state.selectedMangoAccount.current = unOwnedMangoAccount
2022-01-19 13:35:53 -08:00
state.selectedMangoAccount.initialLoad = false
})
actions.fetchTradeHistory()
setResetOnLeave(true)
}
} catch (error) {
2022-03-22 02:18:58 -07:00
console.log('error', error)
router.push('/account')
}
}
if (pubkey) {
2022-01-19 13:35:53 -08:00
setMangoStore((state) => {
state.selectedMangoAccount.initialLoad = true
})
loadUnownedMangoAccount()
}
}, [pubkey, mangoGroup])
useEffect(() => {
const handleRouteChange = () => {
if (resetOnLeave) {
setMangoStore((state) => {
2022-03-29 06:02:29 -07:00
state.selectedMangoAccount.current = null
})
}
}
router.events.on('routeChangeStart', handleRouteChange)
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [resetOnLeave])
2021-07-23 07:07:05 -07:00
useEffect(() => {
if (isCopied) {
const timer = setTimeout(() => {
setIsCopied(false)
}, 1500)
return () => clearTimeout(timer)
}
}, [isCopied])
2021-07-25 06:54:25 -07:00
2022-03-01 11:06:02 -08:00
const handleCopyAddress = (address) => {
setIsCopied(true)
copyToClipboard(address)
}
const handleChangeViewIndex = (index) => {
setViewIndex(index)
}
const handleTabChange = (tabName) => {
setActiveTab(tabName)
2022-03-21 04:56:37 -07:00
setViewIndex(TABS.findIndex((t) => t === tabName))
}
2022-03-01 11:06:02 -08:00
useMemo(() => {
setMngoAccrued(
mangoAccount
? mangoAccount.perpAccounts.reduce((acc, perpAcct) => {
return perpAcct.mngoAccrued.add(acc)
}, ZERO_BN)
: ZERO_BN
)
}, [mangoAccount])
2022-03-22 02:18:58 -07:00
useEffect(() => {
if (connecting) {
router.push('/account')
}
}, [connecting, router])
2022-03-01 11:06:02 -08:00
const handleRedeemMngo = async () => {
const mangoClient = useMangoStore.getState().connection.client
2022-03-01 11:06:02 -08:00
const mngoNodeBank =
2022-03-29 06:02:29 -07:00
mangoGroup?.rootBankAccounts?.[MNGO_INDEX]?.nodeBankAccounts?.[0]
2022-03-30 04:08:05 -07:00
if (!mangoAccount || !mngoNodeBank || !mangoGroup || !wallet) {
2022-03-29 06:02:29 -07:00
return
}
2022-03-01 11:06:02 -08:00
try {
2022-03-29 06:02:29 -07:00
const txids = await mangoClient.redeemAllMngo(
2022-03-01 11:06:02 -08:00
mangoGroup,
mangoAccount,
2022-03-30 04:08:05 -07:00
wallet.adapter,
2022-03-01 11:06:02 -08:00
mangoGroup.tokens[MNGO_INDEX].rootBank,
mngoNodeBank.publicKey,
mngoNodeBank.vault
)
actions.reloadMangoAccount()
setMngoAccrued(ZERO_BN)
2022-03-30 04:08:05 -07:00
if (txids) {
for (const txid of txids) {
notify({
title: t('redeem-success'),
description: '',
txid,
})
}
2022-03-31 04:45:19 -07:00
} else {
notify({
title: t('redeem-failure'),
description: t('transaction-failed'),
2022-03-31 04:45:19 -07:00
type: 'error',
})
2022-03-29 06:02:29 -07:00
}
2022-03-01 11:06:02 -08:00
} catch (e) {
notify({
title: t('redeem-failure'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
}
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<PageBodyContainer>
2022-03-21 04:56:37 -07:00
<div className="flex flex-col pt-4 pb-6 md:flex-row md:items-end md:justify-between md:pb-4 md:pt-10">
2021-07-06 17:13:17 -07:00
{mangoAccount ? (
2021-07-23 07:07:05 -07:00
<>
2022-05-10 20:42:41 -07:00
<div className="flex flex-col pb-3 sm:flex-row sm:items-center md:pb-0">
2022-05-10 20:29:35 -07:00
<button
disabled={!!pubkey}
2022-05-10 20:42:41 -07:00
className={`relative mb-2 mr-4 flex h-20 w-20 items-center justify-center rounded-full sm:mb-0 ${
2022-05-10 20:29:35 -07:00
loadingTransaction
? 'animate-pulse bg-th-bkg-4'
: 'bg-th-bkg-button'
}`}
onClick={() => setShowProfilePicModal(true)}
>
2022-05-15 18:24:48 -07:00
<ProfileImage
2022-05-10 20:55:18 -07:00
thumbHeightClass="h-20"
thumbWidthClass="w-20"
placeholderHeightClass="h-12"
placeholderWidthClass="w-12"
/>
2022-05-10 20:29:35 -07:00
<div className="default-transition absolute bottom-0 top-0 left-0 right-0 flex h-full w-full items-center justify-center rounded-full bg-[rgba(0,0,0,0.6)] opacity-0 hover:opacity-100">
<PencilIcon className="h-5 w-5 text-th-fgd-1" />
</div>
</button>
<div>
<div className="mb-1 flex items-center">
<h1 className={`mr-3`}>
{mangoAccount?.name || t('account')}
</h1>
{!pubkey ? (
<IconButton
className="h-7 w-7"
onClick={() => setShowNameModal(true)}
>
<PencilIcon className="h-3.5 w-3.5" />
</IconButton>
) : null}
</div>
<div className="flex h-4 items-center">
<LinkButton
className="flex items-center text-th-fgd-4 no-underline"
onClick={() =>
handleCopyAddress(mangoAccount.publicKey.toString())
}
2022-03-01 11:06:02 -08:00
>
2022-05-10 20:29:35 -07:00
<span className="text-xxs font-normal sm:text-xs">
{mangoAccount.publicKey.toBase58()}
</span>
<DuplicateIcon className="ml-1.5 h-4 w-4" />
</LinkButton>
{isCopied ? (
<span className="ml-2 rounded bg-th-bkg-3 px-1.5 py-0.5 text-xs">
Copied
</span>
) : null}
</div>
<div className="flex items-center text-xxs text-th-fgd-4">
<ExclamationCircleIcon className="mr-1.5 h-4 w-4" />
{t('account-address-warning')}
</div>
2021-12-07 20:00:02 -08:00
</div>
2021-07-23 07:07:05 -07:00
</div>
2022-01-19 13:35:53 -08:00
{!pubkey ? (
2022-04-05 19:25:31 -07:00
<div className="flex items-center space-x-2">
2022-03-01 11:06:02 -08:00
<button
2022-04-05 19:25:31 -07:00
className="flex h-8 w-full items-center justify-center rounded-full bg-th-primary px-3 py-0 text-xs font-bold text-th-bkg-1 hover:brightness-[1.15] focus:outline-none disabled:cursor-not-allowed disabled:bg-th-bkg-4 disabled:text-th-fgd-4 disabled:hover:brightness-100"
2022-03-01 11:06:02 -08:00
disabled={mngoAccrued.eq(ZERO_BN)}
onClick={handleRedeemMngo}
>
<div className="flex items-center whitespace-nowrap">
<GiftIcon className="mr-1.5 h-4 w-4 flex-shrink-0" />
2022-03-29 06:02:29 -07:00
{!mngoAccrued.eq(ZERO_BN) && mangoGroup
2022-03-13 16:25:15 -07:00
? t('claim-x-mngo', {
amount: nativeToUi(
mngoAccrued.toNumber(),
mangoGroup.tokens[MNGO_INDEX].decimals
).toLocaleString(undefined, {
minimumSignificantDigits: 1,
}),
})
: t('zero-mngo-rewards')}
2022-03-01 11:06:02 -08:00
</div>
</button>
2022-03-01 19:30:25 -08:00
<Menu>
{({ open }) => (
2022-04-05 19:25:31 -07:00
<div className="relative sm:w-full">
<Menu.Button className="flex h-8 items-center justify-center rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-2 text-xs font-bold hover:brightness-[1.1] hover:filter sm:w-full">
2022-03-13 16:25:15 -07:00
{t('more')}
2022-03-01 19:30:25 -08:00
<ChevronDownIcon
className={`default-transition h-5 w-5 ${
open
? 'rotate-180 transform'
: 'rotate-360 transform'
2022-03-01 19:30:25 -08:00
}`}
/>
</Menu.Button>
<Transition
appear={true}
show={open}
as={Fragment}
enter="transition-all ease-in duration-200"
enterFrom="opacity-0 transform scale-75"
enterTo="opacity-100 transform scale-100"
leave="transition ease-out duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Menu.Items className="absolute right-0 z-20 mt-1 w-full space-y-1.5 rounded-md bg-th-bkg-3 px-4 py-2.5 sm:w-48">
2022-03-01 19:30:25 -08:00
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
2022-03-01 19:30:25 -08:00
onClick={() => setShowAlertsModal(true)}
>
<div className="flex items-center">
<BellIcon className="mr-1.5 h-4 w-4" />
2022-03-01 19:30:25 -08:00
{t('alerts')}
</div>
</button>
</Menu.Item>
{!isDelegatedAccount ? (
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
2022-03-01 19:30:25 -08:00
onClick={() => setShowDelegateModal(true)}
>
<div className="flex items-center">
<UsersIcon className="mr-1.5 h-4 w-4" />
2022-03-01 19:30:25 -08:00
{t('delegate:set-delegate')}
</div>
</button>
</Menu.Item>
) : null}
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
2022-03-01 19:30:25 -08:00
onClick={() => setShowAccountsModal(true)}
>
<div className="flex items-center">
<SwitchHorizontalIcon className="mr-1.5 h-4 w-4" />
2022-03-13 16:25:15 -07:00
{t('change-account')}
2022-03-01 19:30:25 -08:00
</div>
</button>
</Menu.Item>
{!isDelegatedAccount ? (
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
2022-03-01 19:30:25 -08:00
onClick={() => setShowCloseAccountModal(true)}
>
<div className="flex items-center whitespace-nowrap">
<TrashIcon className="mr-1.5 h-4 w-4 flex-shrink-0" />
2022-03-01 19:30:25 -08:00
{t('close-account:close-account')}
</div>
</button>
</Menu.Item>
) : null}
</Menu.Items>
</Transition>
</div>
)}
</Menu>
2022-01-19 13:35:53 -08:00
</div>
) : null}
2021-07-23 07:07:05 -07:00
</>
) : null}
</div>
2022-03-21 04:56:37 -07:00
<div className="md:rounded-lg md:bg-th-bkg-2 md:p-6">
2022-03-01 11:06:02 -08:00
{mangoAccount ? (
2022-03-21 04:56:37 -07:00
<Tabs
activeTab={activeTab}
onChange={handleTabChange}
tabs={TABS}
/>
2022-03-01 11:06:02 -08:00
) : null}
2021-07-06 17:13:17 -07:00
{mangoAccount ? (
!isMobile ? (
<TabContent activeTab={activeTab} />
) : (
<Swipeable
index={viewIndex}
onChangeIndex={handleChangeViewIndex}
>
<div>
<AccountOverview />
</div>
<div>
<AccountOrders />
</div>
<div>
<AccountHistory />
</div>
<div>
<AccountInterest />
</div>
<div>
<AccountFunding />
</div>
2022-04-20 14:05:55 -07:00
<div>
<AccountPerformancePerToken />
</div>
</Swipeable>
)
) : connected ? (
2022-01-20 09:40:54 -08:00
isLoading ? (
<div className="flex justify-center py-10">
<Loading />
</div>
) : (
<EmptyState
buttonText={t('create-account')}
icon={<CurrencyDollarIcon />}
onClickButton={() => setShowAccountsModal(true)}
title={t('no-account-found')}
2022-03-22 02:18:58 -07:00
disabled={!wallet || !mangoGroup}
2022-01-20 09:40:54 -08:00
/>
)
) : (
<EmptyState
buttonText={t('connect')}
desc={t('connect-view')}
2022-03-22 02:18:58 -07:00
disabled={!wallet || !mangoGroup}
icon={<LinkIcon />}
2022-03-22 02:18:58 -07:00
onClickButton={handleConnect}
title={t('connect-wallet')}
/>
)}
</div>
2022-03-24 10:03:12 -07:00
{!connected && (
<div className="mt-6 md:mt-3 md:rounded-lg md:bg-th-bkg-2 md:p-6">
2022-03-25 02:48:51 -07:00
<MangoAccountLookup />
2022-03-24 10:03:12 -07:00
</div>
)}
</PageBodyContainer>
{showAccountsModal ? (
<AccountsModal
onClose={handleCloseAccounts}
isOpen={showAccountsModal}
/>
) : null}
2021-07-23 07:07:05 -07:00
{showNameModal ? (
<AccountNameModal
2021-07-30 13:37:00 -07:00
accountName={mangoAccount?.name}
2021-07-23 07:07:05 -07:00
isOpen={showNameModal}
onClose={handleCloseNameModal}
/>
) : null}
2021-11-08 08:42:55 -08:00
{showCloseAccountModal ? (
<CloseAccountModal
isOpen={showCloseAccountModal}
onClose={handleCloseCloseAccountModal}
/>
) : null}
2022-01-18 16:06:27 -08:00
{showAlertsModal ? (
<CreateAlertModal
isOpen={showAlertsModal}
onClose={handleCloseAlertModal}
/>
) : null}
2022-02-09 07:11:51 -08:00
{showDelegateModal ? (
<DelegateModal
delegate={mangoAccount?.delegate}
isOpen={showDelegateModal}
onClose={handleCloseDelegateModal}
/>
) : null}
2022-05-10 20:29:35 -07:00
{showProfilePicModal ? (
<NftProfilePicModal
isOpen={showProfilePicModal}
onClose={handleCloseProfilePicModal}
/>
) : null}
</div>
)
}
const TabContent = ({ activeTab }) => {
switch (activeTab) {
case 'Portfolio':
return <AccountOverview />
case 'Orders':
return <AccountOrders />
case 'History':
return <AccountHistory />
case 'Interest':
return <AccountInterest />
2021-10-17 19:34:24 -07:00
case 'Funding':
return <AccountFunding />
2022-04-20 14:05:55 -07:00
case 'Performance':
2022-04-25 09:53:49 -07:00
return <AccountPerformancePerToken />
default:
return <AccountOverview />
}
}