add account ui tour

This commit is contained in:
saml33 2024-02-12 14:14:47 +11:00
parent 0ae9c546ea
commit 346d49c37a
14 changed files with 276 additions and 37 deletions

View File

@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import Tps, { StatusDot } from './Tps'
import DiscordIcon from './icons/DiscordIcon'
import { TwitterIcon } from './icons/TwitterIcon'
import { DocumentTextIcon } from '@heroicons/react/20/solid'
import { DocumentTextIcon, MapIcon } from '@heroicons/react/20/solid'
import { useEffect, useMemo, useState } from 'react'
import { IDL } from '@blockworks-foundation/mango-v4'
import RpcPing from './RpcPing'
@ -12,6 +12,10 @@ import mangoStore from '@store/mangoStore'
import { Connection } from '@solana/web3.js'
import { sumBy } from 'lodash'
import useInterval from './shared/useInterval'
import { LinkButton } from './shared/Button'
import { useRouter } from 'next/router'
import { startAccountTour } from 'utils/tours'
import useMangoAccount from 'hooks/useMangoAccount'
const DEFAULT_LATEST_COMMIT = { sha: '', url: '' }
export const tpsAlertThreshold = 1300
@ -92,6 +96,9 @@ const getOverallStatus = (
const StatusBar = ({ collapsed }: { collapsed: boolean }) => {
const { t } = useTranslation('common')
const { mangoAccountAddress } = useMangoAccount()
const accountPageTab = mangoStore((s) => s.accountPageTab)
const router = useRouter()
const [latestCommit, setLatestCommit] = useState(DEFAULT_LATEST_COMMIT)
const { offchainHealth, isLoading: loadingOffchainHealth } =
useOffchainServicesHealth()
@ -208,6 +215,17 @@ const StatusBar = ({ collapsed }: { collapsed: boolean }) => {
) : null}
</div>
<div className="col-span-1 flex items-center justify-end space-x-4 text-xs">
{router?.asPath === '/' &&
!router?.query?.view &&
accountPageTab === 'overview' ? (
<LinkButton
className="flex items-center text-th-fgd-3 md:hover:text-th-fgd-2"
onClick={() => startAccountTour(mangoAccountAddress)}
>
<MapIcon className="mr-1 h-3 w-3" />
<span className="font-normal">UI Tour</span>
</LinkButton>
) : null}
{/* {router.asPath.includes('/trade') ? (
<a
className="flex items-center text-th-fgd-3 focus:outline-none md:hover:text-th-fgd-2"

View File

@ -26,7 +26,7 @@ import Tooltip from './shared/Tooltip'
import { copyToClipboard } from 'utils'
import mangoStore from '@store/mangoStore'
import UserSetupModal from './modals/UserSetupModal'
import { IS_ONBOARDED_KEY } from 'utils/constants'
import { IS_ONBOARDED_KEY, UI_TOURS_KEY } from 'utils/constants'
import useLocalStorageState from 'hooks/useLocalStorageState'
import SettingsModal from './modals/SettingsModal'
import DepositWithdrawIcon from './icons/DepositWithdrawIcon'
@ -44,6 +44,7 @@ import TopBarStore from '@store/topBarStore'
import MedalIcon from './icons/MedalIcon'
import BridgeModal from './modals/BridgeModal'
import { useViewport } from 'hooks/useViewport'
import { TOURS, startAccountTour } from 'utils/tours'
export const TOPBAR_ICON_BUTTON_CLASSES =
'relative flex h-16 w-10 sm:w-16 items-center justify-center sm:border-l sm:border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2'
@ -67,6 +68,7 @@ const TopBar = () => {
} = useAccountPointsAndRank(mangoAccountAddress, seasonPointsToFetchId)
const { data: isWhiteListed } = useIsWhiteListed()
const router = useRouter()
const { asPath, query } = useRouter()
const themeData = mangoStore((s) => s.themeData)
const [action, setAction] = useState<'deposit' | 'withdraw'>('deposit')
@ -81,13 +83,21 @@ const TopBar = () => {
const { isUnownedAccount } = useUnownedAccount()
const showUserSetup = mangoStore((s) => s.showUserSetup)
const [, setIsOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
const [seenAccountTours] = useLocalStorageState(UI_TOURS_KEY, [])
const handleCloseSetup = useCallback(() => {
set((s) => {
s.showUserSetup = false
})
setIsOnboarded(true)
}, [setIsOnboarded])
if (
asPath === '/' &&
!query?.view &&
!seenAccountTours.includes(TOURS.ACCOUNT)
) {
startAccountTour(mangoAccountAddress)
}
}, [setIsOnboarded, seenAccountTours, asPath, query, mangoAccountAddress])
const handleShowSetup = useCallback(() => {
set((s) => {

View File

@ -79,8 +79,11 @@ const AccountHeroStats = ({ accountValue }: { accountValue: number }) => {
return (
<>
<div className="border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6">
<div id="account-step-four">
<div
className="border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6"
id="account-health"
>
<div>
<div className="flex justify-between">
<Tooltip
maxWidth="20rem"
@ -167,8 +170,11 @@ const AccountHeroStats = ({ accountValue }: { accountValue: number }) => {
</span>
</div>
</div>
<div className="flex border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6">
<div id="account-step-five">
<div
className="flex border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6"
id="account-free-collateral"
>
<div>
<Tooltip
content={t('account:tooltip-free-collateral')}
maxWidth="20rem"
@ -216,7 +222,7 @@ const AccountHeroStats = ({ accountValue }: { accountValue: number }) => {
</div>
</div>
<div
id="account-step-seven"
id="account-pnl"
className="border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6"
>
<div className="flex items-center justify-between">
@ -258,6 +264,7 @@ const AccountHeroStats = ({ accountValue }: { accountValue: number }) => {
</div>
<button
className="default-transition flex h-10 w-full items-center justify-between px-4 focus:outline-none disabled:cursor-not-allowed md:px-6 md:hover:bg-th-bkg-2"
id="account-more-stats"
onClick={() => handleGoToStats()}
disabled={!mangoAccountAddress}
>

View File

@ -71,9 +71,12 @@ const AccountOverview = () => {
<>
<div className="grid grid-cols-12 border-b border-th-bkg-3">
<div className="col-span-12 border-b border-th-bkg-3 md:col-span-8 md:border-b-0 md:border-r">
<div className="flex h-full w-full flex-col justify-between">
<div
className="flex h-full w-full flex-col justify-between"
id="account-chart"
>
{mangoAccount || (connected && initialLoad) ? (
<div className="overflow-x-hidden px-4 py-4 md:px-6">
<div className="overflow-x-hidden p-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={chartData}

View File

@ -1,11 +1,11 @@
import { useMemo, useState } from 'react'
import { useMemo } from 'react'
import TabButtons from '../shared/TabButtons'
import TokenList from '../TokenList'
import UnsettledTrades from '@components/trade/UnsettledTrades'
import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
import { useViewport } from 'hooks/useViewport'
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
import mangoStore from '@store/mangoStore'
import mangoStore, { AccountPageTab } from '@store/mangoStore'
import PerpPositions from '@components/trade/PerpPositions'
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
import HistoryTabs from './HistoryTabs'
@ -15,7 +15,8 @@ import AccountOverview from './AccountOverview'
import AccountOrders from './AccountOrders'
const AccountTabs = () => {
const [activeTab, setActiveTab] = useState('overview')
const activeTab = mangoStore((s) => s.accountPageTab)
const set = mangoStore.getState().set
const { mangoAccount } = useMangoAccount()
const { isMobile, isTablet } = useViewport()
const unsettledSpotBalances = useUnsettledSpotBalances()
@ -23,7 +24,7 @@ const AccountTabs = () => {
const { openPerpPositions } = useOpenPerpPositions()
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
const tabsWithCount: [string, number][] = useMemo(() => {
const tabsWithCount: [AccountPageTab, number][] = useMemo(() => {
const unsettledTradeCount =
Object.values(unsettledSpotBalances).flat().length +
unsettledPerpPositions?.length
@ -32,7 +33,7 @@ const AccountTabs = () => {
mangoAccount?.tokenConditionalSwaps.filter((tcs) => tcs.isConfigured)
?.length || 0
const tabs: [string, number][] = [
const tabs: [AccountPageTab, number][] = [
['overview', 0],
['balances', 0],
['trade:positions', openPerpPositions.length],
@ -54,16 +55,22 @@ const AccountTabs = () => {
return (
<>
<div className="hide-scroll flex items-center overflow-x-auto border-b border-th-bkg-3">
<TabButtons
activeValue={activeTab}
onChange={(v) => setActiveTab(v)}
values={tabsWithCount}
showBorders
fillWidth={isMobile || isTablet}
/>
<div className="hide-scroll flex items-center justify-between overflow-x-auto border-b border-th-bkg-3">
<div className="w-full md:w-auto" id="account-tabs">
<TabButtons
activeValue={activeTab}
onChange={(v: AccountPageTab) =>
set((state) => {
state.accountPageTab = v
})
}
values={tabsWithCount}
showBorders
fillWidth={isMobile || isTablet}
/>
</div>
<ManualRefresh
classNames="fixed bottom-16 right-4 md:relative md:px-2 lg:px-0 lg:pr-6 md:bottom-0 md:right-0 z-10 shadow-lg md:shadow-none bg-th-bkg-3 md:bg-transparent"
classNames="fixed bottom-16 right-4 md:relative md:pr-2 lg:pr-4 md:bottom-0 md:right-0 z-10 shadow-lg md:shadow-none bg-th-bkg-3 md:bg-transparent"
hideBg={isMobile || isTablet}
size={isTablet ? 'large' : 'small'}
/>

View File

@ -49,6 +49,7 @@ const Explore = () => {
className={`flex h-10 flex-col items-center justify-end md:items-start ${
activeTab === 'tokens' ? 'mb-4 lg:mb-0' : ''
}`}
id="account-explore-tabs"
>
<TabsText
activeTab={activeTab}

View File

@ -43,16 +43,18 @@ const ManualRefresh = ({
return (
<div className={`${classNames} rounded-full`}>
<Tooltip content={t('refresh-data')} className="py-1 text-xs">
<IconButton
hideBg={hideBg}
onClick={handleRefreshData}
disabled={spin}
size={size}
>
<ArrowPathIcon
className={`h-5 w-5 ${spin ? 'animate-spin' : null}`}
/>
</IconButton>
<div id="account-refresh">
<IconButton
hideBg={hideBg}
onClick={handleRefreshData}
disabled={spin}
size={size}
>
<ArrowPathIcon
className={`h-5 w-5 ${spin ? 'animate-spin' : null}`}
/>
</IconButton>
</div>
</Tooltip>
</div>
)

View File

@ -54,6 +54,7 @@
"date-fns": "2.29.3",
"dayjs": "1.11.3",
"decimal.js": "10.4.0",
"driver.js": "1.3.1",
"howler": "2.2.3",
"html-react-parser": "3.0.4",
"html2canvas": "1.4.1",

View File

@ -1,6 +1,7 @@
import '../styles/globals.css'
import 'react-nice-dates/build/style.css'
import '../styles/datepicker.css'
import 'driver.js/dist/driver.css'
import type { AppProps } from 'next/app'
import { useCallback, useMemo } from 'react'
import {

View File

@ -184,7 +184,16 @@ export const DEFAULT_TRADE_FORM: TradeForm = {
reduceOnly: false,
}
export type AccountPageTab =
| 'overview'
| 'balances'
| 'trade:positions'
| 'trade:orders'
| 'trade:unsettled'
| 'history'
export type MangoStore = {
accountPageTab: AccountPageTab
activityFeed: {
feed: Array<ActivityFeed>
loading: boolean
@ -378,6 +387,7 @@ const mangoStore = create<MangoStore>()(
)
return {
accountPageTab: 'overview',
activityFeed: {
feed: [],
loading: true,

View File

@ -840,3 +840,59 @@ a.page-link:hover {
.recharts-layer .recharts-pie-sector {
@apply focus:outline-none;
}
/* ui tour */
.driver-popover.ui-tour {
@apply bg-th-bkg-3 p-6;
}
.driver-popover.ui-tour .driver-popover-title,
.driver-popover.ui-tour .driver-popover-description {
@apply font-body text-th-fgd-1;
}
.driver-popover.ui-tour .driver-popover-description {
@apply mb-6;
}
.driver-popover.ui-tour .driver-popover-progress-text {
@apply text-th-active;
}
.driver-popover.ui-tour .driver-popover-navigation-btns {
@apply gap-1;
}
.driver-popover.ui-tour button {
@apply default-transition rounded-md border-transparent bg-th-button px-3 py-2 font-bold text-th-fgd-1 focus:outline-none;
text-shadow: none;
}
.driver-popover.ui-tour button:hover {
@apply bg-th-button-hover;
}
.driver-popover.ui-tour .driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: var(--bkg-3);
}
.driver-popover.ui-tour .driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: var(--bkg-3);
}
.driver-popover.ui-tour .driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: var(--bkg-3);
}
.driver-popover.ui-tour .driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: var(--bkg-3);
}
.driver-popover.ui-tour .driver-popover-close-btn {
@apply bg-transparent p-0 text-th-fgd-3;
}
.driver-popover.ui-tour .driver-popover-close-btn:hover {
@apply bg-transparent text-th-fgd-1;
}

View File

@ -23,7 +23,7 @@ export const SHOW_ZERO_BALANCES_KEY = 'show-zero-balances-0.2'
export const SIDEBAR_COLLAPSE_KEY = 'sidebar-0.1'
export const ONBOARDING_TOUR_KEY = 'showOnboardingTour-0.1'
export const UI_TOURS_KEY = 'uiToursCompleted-0.1'
export const PREFERRED_EXPLORER_KEY = 'preferredExplorer-0.1'

118
utils/tours.ts Normal file
View File

@ -0,0 +1,118 @@
import { driver } from 'driver.js'
import { ttCommons } from './fonts'
import { UI_TOURS_KEY } from './constants'
export enum TOURS {
ACCOUNT,
SWAP,
TRADE,
}
// function to create account tour with dynamic steps
const createAccountTour = (mangoAccountPk: string | undefined) => {
return driver({
nextBtnText: 'Next',
prevBtnText: 'Previous',
doneBtnText: 'Done',
popoverClass: 'ui-tour',
showProgress: true,
onPopoverRender: () => {
const fonts = document.getElementById('driver-popover-content')
if (fonts) {
fonts.classList.add(ttCommons.variable, 'font-sans')
}
},
onDestroyed: () => {
const completedToursString = localStorage.getItem(UI_TOURS_KEY)
const completedTours = completedToursString
? JSON.parse(completedToursString)
: []
if (!completedTours.includes(TOURS.ACCOUNT)) {
localStorage.setItem(
UI_TOURS_KEY,
JSON.stringify([...completedTours, TOURS.ACCOUNT]),
)
}
},
steps: [
{
popover: {
title: 'Take the Tour',
description: "We'll show you the ropes of your account page.",
},
},
{
element: '#account-tabs',
popover: {
title: 'Mission Control',
description:
'Detailed views for everything you need to manage your account.',
},
},
{
element: '#account-refresh',
popover: {
title: 'Feeling Refreshed Yet?',
description:
'Your account data will update automatically but you can also manually refresh it here.',
},
},
{
element: '#account-chart',
popover: {
title: mangoAccountPk
? 'Your Mango Net Worth'
: 'Account value chart',
description: mangoAccountPk
? 'The value and trend of your Mango Account with quick action buttons.'
: 'When you create your account a chart of the value will show here.',
},
},
{
element: '#account-health',
popover: {
title: 'Health is Wealth',
description:
'If you use margin you need to keep your account health above 0% to prevent liquidation. Hit the bar chart icon for a detailed view.',
},
},
{
element: '#account-free-collateral',
popover: {
title: 'Your Bankroll',
description:
"The amount of capital you have to use for trades and loans. If your free collateral reaches $0 you won't be able to open new positions, borrow or withdraw collateral.",
},
},
{
element: '#account-pnl',
popover: {
title: 'Are ya Winning?',
description:
'How much $ your account has made or lost. PnL accounts for changes in the spot prices of your deposits and your positions. Hit the calendar icon to view your PnL history.',
},
},
{
element: '#account-more-stats',
popover: {
title: "But Wait. There's More...",
description:
'Charts and historical stats for PnL, interest, funding and volume.',
},
},
{
element: '#account-explore-tabs',
popover: {
title: 'Explore Mango',
description:
'Stats on our listed tokens and markets and your followed accounts. Visit the leaderboard to start finding accounts to follow.',
},
},
],
})
}
export const startAccountTour = (mangoAccountPk: string | undefined) => {
const accountTour = createAccountTour(mangoAccountPk)
accountTour.drive()
}

View File

@ -2553,7 +2553,7 @@
dependencies:
"@solana/wallet-adapter-base" "^0.9.23"
"@solana/wallet-adapter-solflare@0.6.27":
"@solana/wallet-adapter-solflare@0.6.27", "@solana/wallet-adapter-solflare@^0.6.28":
version "0.6.27"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e"
integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg==
@ -2564,7 +2564,7 @@
"@solflare-wallet/sdk" "^1.3.0"
"@wallet-standard/wallet" "^1.0.1"
"@solana/wallet-adapter-solflare@0.6.28", "@solana/wallet-adapter-solflare@^0.6.28":
"@solana/wallet-adapter-solflare@0.6.28":
version "0.6.28"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2"
integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw==
@ -6033,6 +6033,11 @@ draggabilly@^3.0.0:
get-size "^3.0.0"
unidragger "^3.0.0"
driver.js@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/driver.js/-/driver.js-1.3.1.tgz#5ac4d0d9e1c60f7eade418992ceffef7bf6c6610"
integrity sha512-MvUdXbqSgEsgS/H9KyWb5Rxy0aE6BhOVT4cssi2x2XjmXea6qQfgdx32XKVLLSqTaIw7q/uxU5Xl3NV7+cN6FQ==
duplexify@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0"