diff --git a/components/Layout.tsx b/components/Layout.tsx index 662e01c6..d6005bbb 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -8,7 +8,12 @@ import BottomBar from './mobile/BottomBar' import BounceLoader from './shared/BounceLoader' import TopBar from './TopBar' import useLocalStorageState from '../hooks/useLocalStorageState' -import { SIDEBAR_COLLAPSE_KEY } from '../utils/constants' +import { + IS_ONBOARDED_KEY, + ONBOARDING_TOUR_KEY, + SIDEBAR_COLLAPSE_KEY, +} from '../utils/constants' +import OnboardingTour from './OnboardingTour' const Layout = ({ children }: { children: ReactNode }) => { const connected = mangoStore((s) => s.connected) @@ -17,6 +22,8 @@ const Layout = ({ children }: { children: ReactNode }) => { SIDEBAR_COLLAPSE_KEY, false ) + const [showOnboardingTour] = useLocalStorageState(ONBOARDING_TOUR_KEY, false) + const [isOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY) const { width } = useViewport() useEffect(() => { @@ -77,6 +84,9 @@ const Layout = ({ children }: { children: ReactNode }) => { + {showOnboardingTour && isOnboarded && connected ? ( + + ) : null} ) } diff --git a/components/MangoAccountsList.tsx b/components/MangoAccountsList.tsx index 7546932b..79de5160 100644 --- a/components/MangoAccountsList.tsx +++ b/components/MangoAccountsList.tsx @@ -13,14 +13,16 @@ import { useLocalStorageStringState } from '../hooks/useLocalStorageState' import { LAST_ACCOUNT_KEY } from '../utils/constants' import { useTranslation } from 'next-i18next' import { retryFn } from '../utils' +import Loading from './shared/Loading' const MangoAccountsList = ({ mangoAccount, }: { - mangoAccount: MangoAccount + mangoAccount: MangoAccount | undefined }) => { const { t } = useTranslation('common') const mangoAccounts = mangoStore((s) => s.mangoAccounts.accounts) + const loading = mangoStore((s) => s.mangoAccount.initialLoad) const [showNewAccountModal, setShowNewAccountModal] = useState(false) const [, setLastAccountViewed] = useLocalStorageStringState(LAST_ACCOUNT_KEY) @@ -45,7 +47,7 @@ const MangoAccountsList = ({ } return ( - <> +
{({ open }) => ( <> @@ -53,7 +55,7 @@ const MangoAccountsList = ({

{t('accounts')}

- {mangoAccount.name} + {mangoAccount ? mangoAccount.name : 'No Accounts'}

- {mangoAccounts.length ? ( + {loading ? ( + + ) : mangoAccounts.length ? ( mangoAccounts.map((acc) => (
)) ) : ( -

Loading...

+

+ Create your first account 😎 +

)}
setShowNewAccountModal(false)} /> ) : null} - +
) } diff --git a/components/OnboardingTour.tsx b/components/OnboardingTour.tsx new file mode 100644 index 00000000..ec744b7e --- /dev/null +++ b/components/OnboardingTour.tsx @@ -0,0 +1,176 @@ +import { XMarkIcon } from '@heroicons/react/20/solid' +import { useRouter } from 'next/router' +import { + CardinalOrientation, + MaskOptions, + Walktour, + WalktourLogic, +} from 'walktour' +import useLocalStorageState from '../hooks/useLocalStorageState' +import { ONBOARDING_TOUR_KEY } from '../utils/constants' + +const OnboardingTour = () => { + const [, setShowOnboardingTour] = useLocalStorageState(ONBOARDING_TOUR_KEY) + const router = useRouter() + + const renderTooltip = (tourLogic: WalktourLogic | undefined) => { + const { title, description } = tourLogic!.stepContent + const { next, prev, close, allSteps, stepIndex } = tourLogic! + + const handleClose = () => { + setShowOnboardingTour(false) + close() + } + + return ( +
+ +

{title}

+

{description}

+
+ {stepIndex !== 0 ? ( + + ) : ( +
+ )} +
+ {allSteps.map((s, i) => ( +
+ ))} +
+ {stepIndex !== allSteps.length - 1 ? ( + + ) : ( + + )} +
+
+ ) + } + + const steps = [ + { + selector: '#step-one', + title: 'Your Accounts', + description: + 'Switch between accounts and create new ones. Use multiple accounts to trade isolated margin and protect your capital from liquidation.', + orientationPreferences: [CardinalOrientation.SOUTHEAST], + movingTarget: true, + }, + { + selector: '#step-two', + title: 'Account Value', + description: + 'The value of your assets (deposits) minus the value of your liabilities (borrows).', + orientationPreferences: [CardinalOrientation.EASTNORTH], + movingTarget: true, + }, + { + selector: '#step-three', + title: 'Health', + description: + 'If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.', + orientationPreferences: [CardinalOrientation.SOUTHWEST], + movingTarget: true, + }, + { + selector: '#step-four', + title: 'Free Collateral', + description: + "The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.", + orientationPreferences: [CardinalOrientation.SOUTHWEST], + movingTarget: true, + }, + { + selector: '#step-five', + title: 'Total Interest Value', + description: + 'The value of interest earned (deposits) minus interest paid (borrows).', + orientationPreferences: [CardinalOrientation.SOUTHWEST], + movingTarget: true, + }, + { + selector: '#step-six', + title: 'Health Check', + description: + 'Check the health of your account from any screen in the app. A green heart represents good health, orange okay and red poor.', + orientationPreferences: [CardinalOrientation.EASTSOUTH], + movingTarget: true, + customNextFunc: (tourLogic: WalktourLogic) => { + router.push('/trade') + setTimeout(() => tourLogic.next(), 1000) + }, + }, + { + selector: '#step-seven', + title: 'Trade', + description: + "You choose the quote token of your trades. This means you can easily trade tokens on their relative strength vs. another token. Let's say your thesis is BTC will see diminishing returns relative to SOL. You can sell BTC and buy SOL. Now you are long SOL/BTC", + orientationPreferences: [CardinalOrientation.CENTER], + customPrevFunc: (tourLogic: WalktourLogic) => { + router.push('/') + tourLogic.prev() + }, + }, + { + selector: '#step-eight', + title: 'Trade Settings', + description: + 'Edit your slippage settings and toggle margin on and off. When margin is off your trades will be limited by your balance for each token.', + orientationPreferences: [CardinalOrientation.WESTNORTH], + movingTarget: true, + }, + { + selector: '#step-nine', + title: 'Token to Sell', + description: + 'Select the token you want to sell. If your sell size is above your token balance a loan will be opened to cover the shortfall.', + orientationPreferences: [CardinalOrientation.WESTNORTH], + movingTarget: true, + }, + { + selector: '#step-ten', + title: 'Health Impact', + description: + 'Projects the health of your account before you make a trade.', + orientationPreferences: [CardinalOrientation.WESTNORTH], + movingTarget: true, + }, + ] + + return ( + + ) +} + +export default OnboardingTour diff --git a/components/SideNav.tsx b/components/SideNav.tsx index 8ca3c921..99d4a621 100644 --- a/components/SideNav.tsx +++ b/components/SideNav.tsx @@ -9,6 +9,7 @@ import { CurrencyDollarIcon, ChartBarIcon, Cog8ToothIcon, + InformationCircleIcon, ArrowsRightLeftIcon, } from '@heroicons/react/20/solid' import { useRouter } from 'next/router' @@ -17,12 +18,26 @@ import { Fragment, ReactNode, useEffect, useState } from 'react' import { Disclosure, Popover, Transition } from '@headlessui/react' import MangoAccountSummary from './account/MangoAccountSummary' import Tooltip from './shared/Tooltip' +import { HealthType } from '@blockworks-foundation/mango-v4' +import { useWallet } from '@solana/wallet-adapter-react' +import useLocalStorageState from '../hooks/useLocalStorageState' +import { ONBOARDING_TOUR_KEY } from '../utils/constants' const SideNav = ({ collapsed }: { collapsed: boolean }) => { + const [, setShowOnboardingTour] = useLocalStorageState(ONBOARDING_TOUR_KEY) const { t } = useTranslation('common') + const { connected } = useWallet() + const mangoAccount = mangoStore((s) => s.mangoAccount.current) const router = useRouter() const { pathname } = router + const handleTakeTour = () => { + if (pathname !== '/') { + router.push('/') + } + setShowOnboardingTour(true) + } + return (
{ isExternal showTooltip={false} /> + {connected ? ( + + ) : null}
- +
+ + } + title={ +
+

Health Check

+

+ {mangoAccount + ? mangoAccount.name + : connected + ? 'No Account' + : 'Connect'} +

+
+ } + alignBottom + hideIconBg + > +
+ +
+
+
) } diff --git a/components/TopBar.tsx b/components/TopBar.tsx index cdc5f6a0..1d466c12 100644 --- a/components/TopBar.tsx +++ b/components/TopBar.tsx @@ -50,9 +50,7 @@ const TopBar = () => { {connected ? (
- {mangoAccount ? ( - - ) : null} +
) : isOnboarded ? ( diff --git a/components/account/AccountPage.tsx b/components/account/AccountPage.tsx index 77f27906..096efd6e 100644 --- a/components/account/AccountPage.tsx +++ b/components/account/AccountPage.tsx @@ -130,14 +130,14 @@ const AccountPage = () => { }, [totalInterestData]) const maintHealth = useMemo(() => { - return mangoAccount ? mangoAccount.getHealthRatioUi(HealthType.maint) : 100 + return mangoAccount ? mangoAccount.getHealthRatioUi(HealthType.maint) : 0 }, [mangoAccount]) return !chartToShow ? ( <>
-
+

{t('account-value')}

$ @@ -221,21 +221,25 @@ const AccountPage = () => {
-

{t('health')}

-

{maintHealth}%

+
+

{t('health')}

+

{maintHealth}%

+
-

{t('free-collateral')}

-

- {mangoAccount - ? formatFixedDecimals( - toUiDecimalsForQuote( - mangoAccount.getCollateralValue()!.toNumber() - ), - true - ) - : (0).toFixed(2)} -

+
+

{t('free-collateral')}

+

+ {mangoAccount + ? formatFixedDecimals( + toUiDecimalsForQuote( + mangoAccount.getCollateralValue()!.toNumber() + ), + true + ) + : (0).toFixed(2)} +

+
{/*
@@ -254,7 +258,7 @@ const AccountPage = () => { ) : null}
*/}
-
+

{t('total-interest-value')}

{formatFixedDecimals(interestTotalValue, true)} diff --git a/components/account/HealthHeart.tsx b/components/account/HealthHeart.tsx index 10b24adc..10af6f70 100644 --- a/components/account/HealthHeart.tsx +++ b/components/account/HealthHeart.tsx @@ -1,4 +1,10 @@ -const HealthHeart = ({ health, size }: { health: number; size: number }) => { +const HealthHeart = ({ + health, + size, +}: { + health: number | undefined + size: number +}) => { const styles = { height: `${size}px`, width: `${size}px`, @@ -6,13 +12,16 @@ const HealthHeart = ({ health, size }: { health: number; size: number }) => { return ( 15 && health < 50 - ? 'text-th-orange' - : health >= 50 - ? 'text-th-green' - : 'text-th-red' + health + ? health > 15 && health < 50 + ? 'text-th-orange' + : health >= 50 + ? 'text-th-green' + : 'text-th-red' + : 'text-th-fgd-4' } style={styles} viewBox="0 0 20 20" @@ -30,7 +39,13 @@ const HealthHeart = ({ health, size }: { health: number; size: number }) => { keyTimes="0;0.5;1" values="1;1.1;1" dur={ - health > 15 && health < 50 ? '1s' : health >= 50 ? '2s' : '0.33s' + health + ? health > 15 && health < 50 + ? '1s' + : health >= 50 + ? '2s' + : '0.33s' + : '0s' } repeatCount="indefinite" /> @@ -38,7 +53,13 @@ const HealthHeart = ({ health, size }: { health: number; size: number }) => { attributeName="opacity" values="0.8;1;0.8" dur={ - health > 15 && health < 50 ? '1s' : health >= 50 ? '2s' : '0.33s' + health + ? health > 15 && health < 50 + ? '1s' + : health >= 50 + ? '2s' + : '0.33s' + : '0s' } repeatCount="indefinite" /> diff --git a/components/account/MangoAccountSummary.tsx b/components/account/MangoAccountSummary.tsx index 6fa4156e..c3f694f6 100644 --- a/components/account/MangoAccountSummary.tsx +++ b/components/account/MangoAccountSummary.tsx @@ -10,89 +10,64 @@ import DepositModal from '../modals/DepositModal' import WithdrawModal from '../modals/WithdrawModal' import { useTranslation } from 'next-i18next' import { ArrowDownTrayIcon } from '@heroicons/react/20/solid' +import { useWallet } from '@solana/wallet-adapter-react' import HealthHeart from './HealthHeart' import { ExpandableMenuItem } from '../SideNav' const MangoAccountSummary = ({ collapsed }: { collapsed: boolean }) => { const { t } = useTranslation('common') + const { connected } = useWallet() const mangoAccount = mangoStore((s) => s.mangoAccount.current) const [showDepositModal, setShowDepositModal] = useState(false) const [showWithdrawModal, setShowWithdrawModal] = useState(false) return ( <> - {mangoAccount ? ( -

- - } - title={ -
-

Health Check

-

- {mangoAccount.name} -

-
- } - alignBottom - hideIconBg - > -
-
-
-

{t('health')}

-

- {mangoAccount - ? mangoAccount.getHealthRatioUi(HealthType.maint) - : 100} - % -

-
-
-

{t('account-value')}

-

- $ - {mangoAccount - ? formatDecimal( - toUiDecimalsForQuote( - mangoAccount.getEquity()!.toNumber() - ), - 2 - ) - : (0).toFixed(2)} -

-
-
-

- {t('free-collateral')} -

-

- $ - {mangoAccount - ? formatDecimal( - toUiDecimalsForQuote( - mangoAccount.getCollateralValue()!.toNumber() - ), - 2 - ) - : (0).toFixed(2)} -

-
-
-
- - {/*
+
+ + {/* setShowSetupStep(4)} > Skip for now @@ -562,7 +564,7 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => { Deposit
- + setShowSetupStep(4)}> Skip for now @@ -571,6 +573,46 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
)} + +
+
+

That's a wrap

+

+ We recommend taking a short tour to get familiar with our + new interface. +

+

+ Or, jump in the deep end and start trading. +

+
+
+ + +
+
+
diff --git a/components/swap/SwapForm.tsx b/components/swap/SwapForm.tsx index 6d0f7ab1..94374461 100644 --- a/components/swap/SwapForm.tsx +++ b/components/swap/SwapForm.tsx @@ -239,15 +239,17 @@ const SwapForm = () => {

{t('trade')}

- setShowSettings(true)} - size="small" - > - - +
+ setShowSettings(true)} + size="small" + > + + +
-
+

{t('sell')}

{
- {!!mangoAccount ? ( -
-
-

{t('health-impact')}

-
-

{currentMaintHealth}%

- -

15 - ? 'text-th-orange' - : maintProjectedHealth! <= 15 - ? 'text-th-red' - : 'text-th-green' - } text-sm`} + {/* {!!mangoAccount ? ( */} +

+
+

{t('health-impact')}

+
+

{currentMaintHealth}%

+ +

15 + ? 'text-th-orange' + : maintProjectedHealth! <= 15 + ? 'text-th-red' + : 'text-th-green' + } text-sm`} + > + {maintProjectedHealth!}%{' '} + = currentMaintHealth! + ? 'text-th-green' + : 'text-th-red' + }`} > - {maintProjectedHealth!}%{' '} - = currentMaintHealth! - ? 'text-th-green' - : 'text-th-red' - }`} - > - ({maintProjectedHealth! >= currentMaintHealth! ? '+' : ''} - {maintProjectedHealth! - currentMaintHealth!}%) - -

-
+ ({maintProjectedHealth! >= currentMaintHealth! ? '+' : ''} + {maintProjectedHealth! - currentMaintHealth!}%) + +

- ) : null} +
+ {/* ) : null} */} ) } diff --git a/package.json b/package.json index 2ec782f4..37e063b6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-window": "^1.8.7", "recharts": "^2.1.12", "tsparticles": "^2.2.4", + "walktour": "^5.1.1", "zustand": "^4.1.1" }, "peerDependencies": { diff --git a/utils/constants.ts b/utils/constants.ts index adaa2553..eeebd172 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -14,6 +14,8 @@ export const ALPHA_DEPOSIT_LIMIT = 20 export const SIDEBAR_COLLAPSE_KEY = 'sidebar-0.1' +export const ONBOARDING_TOUR_KEY = 'showOnboardingTour' + export const PROFILE_CATEGORIES = [ 'borrower', 'day-trader', diff --git a/yarn.lock b/yarn.lock index d0a020c4..2a1d0ad8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5445,6 +5445,11 @@ wait-on@6.0.0: minimist "^1.2.5" rxjs "^7.1.0" +walktour@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/walktour/-/walktour-5.1.1.tgz#951b5bce2abed0ae4209dc74d4d79f252a85e813" + integrity sha512-pRjbjjBGddgiWaWryE+H6DxOeAuUgIZJ5ck0hOVrhZ4QNgZDs1okuGMXDKx3Gi4hi/N9ESwATyyB5hb7kiK7wQ== + web-streams-polyfill@^3.0.3: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"