diff --git a/components/AlphaModal.tsx b/components/AlphaModal.tsx index e2a0e502..d25f68ca 100644 --- a/components/AlphaModal.tsx +++ b/components/AlphaModal.tsx @@ -5,6 +5,7 @@ import Button from './Button' import useLocalStorageState from '../hooks/useLocalStorageState' import { useTranslation } from 'next-i18next' import Checkbox from './Checkbox' +import { SHOW_TOUR_KEY } from './IntroTips' export const ALPHA_MODAL_KEY = 'mangoAlphaAccepted-3.06' @@ -18,11 +19,17 @@ const AlphaModal = ({ const { t } = useTranslation('common') const [acceptRisks, setAcceptRisks] = useState(false) const [, setAlphaAccepted] = useLocalStorageState(ALPHA_MODAL_KEY, false) + const [, setShowTips] = useLocalStorageState(SHOW_TOUR_KEY, false) - const handleAccept = () => { + const handleGetStarted = () => { setAlphaAccepted(true) } + const handleTakeTour = () => { + setAlphaAccepted(true) + setShowTips(true) + } + return ( @@ -71,9 +78,20 @@ const AlphaModal = ({ I understand and accept the risks -
- +
diff --git a/components/ConnectWalletButton.tsx b/components/ConnectWalletButton.tsx index 10de0fb0..438c01f7 100644 --- a/components/ConnectWalletButton.tsx +++ b/components/ConnectWalletButton.tsx @@ -54,7 +54,7 @@ const ConnectWalletButton = () => { <> {connected && wallet?.publicKey ? ( -
+
@@ -104,7 +104,10 @@ const ConnectWalletButton = () => {
) : ( -
+
- } - value={savedLanguage} - onChange={(lang) => handleLangChange(lang)} - options={LANGS} - toolTipContent={t('change-language')} - /> - ) : ( -
+ return ( +
+ {mounted ? ( + + +
+ } + value={savedLanguage} + onChange={(lang) => handleLangChange(lang)} + options={LANGS} + toolTipContent={t('change-language')} + /> + ) : ( +
+ )} +
) } diff --git a/components/MarketDetails.tsx b/components/MarketDetails.tsx index 74a7370c..eb2e5e97 100644 --- a/components/MarketDetails.tsx +++ b/components/MarketDetails.tsx @@ -254,10 +254,14 @@ const MarketDetails = () => {
- {!isMobile ? : null} - {!isMobile && connected && mangoAccount ? ( - + {!isMobile ? ( +
+ +
) : null} +
+ {!isMobile && connected && mangoAccount ? : null} +
) diff --git a/components/MarketPosition.tsx b/components/MarketPosition.tsx index c3a175f8..02bb5823 100644 --- a/components/MarketPosition.tsx +++ b/components/MarketPosition.tsx @@ -152,7 +152,10 @@ export default function MarketPosition() { return ( <> -
+
{!isMobile ? ( {marketConfig.name} {t('position')} diff --git a/components/ThemeSwitch.tsx b/components/ThemeSwitch.tsx index 92c7b6a8..730f3d4d 100644 --- a/components/ThemeSwitch.tsx +++ b/components/ThemeSwitch.tsx @@ -19,20 +19,24 @@ const ThemeSwitch = () => { // When mounted on client, now we can show the UI useEffect(() => setMounted(true), []) - return mounted ? ( - - {THEMES.find((t) => t.name === theme).icon} -
- } - value={theme} - onChange={(theme) => setTheme(theme)} - options={THEMES} - toolTipContent={t('change-theme')} - /> - ) : ( -
+ return ( +
+ {mounted ? ( + + {THEMES.find((t) => t.name === theme).icon} +
+ } + value={theme} + onChange={(theme) => setTheme(theme)} + options={THEMES} + toolTipContent={t('change-theme')} + /> + ) : ( +
+ )} +
) } diff --git a/components/TradingView/index.tsx b/components/TradingView/index.tsx index 3761754f..378bca7e 100755 --- a/components/TradingView/index.tsx +++ b/components/TradingView/index.tsx @@ -47,7 +47,7 @@ const TVChartContainer = () => { const selectedMarketName = selectedMarketConfig.name const openOrders = useOpenOrders() - const actions = useMangoStore((s) => s.actions ) + const actions = useMangoStore((s) => s.actions) const connected = useMangoStore((s) => s.wallet.connected) const selectedMarginAccount = useMangoStore.getState().selectedMangoAccount.current @@ -149,7 +149,6 @@ const TVChartContainer = () => { //eslint-disable-next-line }, [selectedMarketConfig, theme, isMobile]) - const handleCancelOrder = async ( order: Order | PerpOrder | PerpTriggerOrder, market: Market | PerpMarket @@ -204,11 +203,10 @@ const TVChartContainer = () => { actions.reloadMangoAccount() actions.reloadOrders() toggleOrderInProgress(false) - toggleMoveInProgress(false) + toggleMoveInProgress(false) } } - const handleModifyOrder = async ( order: Order | PerpOrder, market: Market | PerpMarket, @@ -283,7 +281,6 @@ const TVChartContainer = () => { } } - function getLine(order, market) { return tvWidgetRef.current .chart() @@ -321,13 +318,9 @@ const TVChartContainer = () => { tvWidgetRef.current.showConfirmDialog({ title: 'Modify Your Order?', body: `Would you like to change your order from a - ${order.size} ${market.config.baseSymbol} ${ - order.side - } at $${currentOrderPrice} + ${order.size} ${market.config.baseSymbol} ${order.side} at $${currentOrderPrice} to a - ${order.size} ${market.config.baseSymbol} LIMIT ${ - order.side - } at $${updatedOrderPrice}? + ${order.size} ${market.config.baseSymbol} LIMIT ${order.side} at $${updatedOrderPrice}? `, callback: (res) => { if (res) { @@ -340,11 +333,10 @@ const TVChartContainer = () => { }, }) } - } else { + } else { tvWidgetRef.current.showNoticeDialog({ title: 'Advanced Order Type', - body: - 'Advanced order types in the chart window may only be cancelled. If new conditions are required, please cancel this order and use the Advanced Trade Form.', + body: 'Advanced order types in the chart window may only be cancelled. If new conditions are required, please cancel this order and use the Advanced Trade Form.', callback: () => { this.setPrice(currentOrderPrice) toggleMoveInProgress(false) @@ -358,9 +350,7 @@ const TVChartContainer = () => { tvWidgetRef.current.showConfirmDialog({ title: 'Cancel Your Order?', body: `Would you like to cancel your order for - ${order.size} ${market.config.baseSymbol} ${ - order.side - } at $${order.price} + ${order.size} ${market.config.baseSymbol} ${order.side} at $${order.price} `, callback: (res) => { if (res) { @@ -374,39 +364,94 @@ const TVChartContainer = () => { .setPrice(order.price) .setQuantity(order.size) .setText(getLineText(order, market)) - .setTooltip(order.perpTrigger?.clientOrderId ? `${order.orderType} Order #: ${order.orderId}` : `Order #: ${order.orderId}`) - .setBodyTextColor(theme === 'Dark' ? '#F2C94C' : theme === 'Light' ? '#FF9C24' : '#F2C94C') - .setQuantityTextColor(theme === 'Dark' ? '#F2C94C' : theme === 'Light' ? '#FF9C24' : '#F2C94C') - .setCancelButtonIconColor(theme === 'Dark' ? '#F2C94C' : theme === 'Light' ? '#FF9C24' : '#F2C94C') - .setBodyBorderColor(order.perpTrigger?.clientOrderId ? '#FF9C24' : order.side == 'buy' ? '#4BA53B' : '#AA2222') - .setQuantityBorderColor(order.perpTrigger?.clientOrderId ? '#FF9C24' : order.side == 'buy' ? '#4BA53B' : '#AA2222') - .setCancelButtonBorderColor(order.perpTrigger?.clientOrderId ? '#FF9C24' : order.side == 'buy' ? '#4BA53B' : '#AA2222') - .setBodyBackgroundColor(theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832') - .setQuantityBackgroundColor(theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832') - .setCancelButtonBackgroundColor(theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832') + .setTooltip( + order.perpTrigger?.clientOrderId + ? `${order.orderType} Order #: ${order.orderId}` + : `Order #: ${order.orderId}` + ) + .setBodyTextColor( + theme === 'Dark' ? '#F2C94C' : theme === 'Light' ? '#FF9C24' : '#F2C94C' + ) + .setQuantityTextColor( + theme === 'Dark' ? '#F2C94C' : theme === 'Light' ? '#FF9C24' : '#F2C94C' + ) + .setCancelButtonIconColor( + theme === 'Dark' ? '#F2C94C' : theme === 'Light' ? '#FF9C24' : '#F2C94C' + ) + .setBodyBorderColor( + order.perpTrigger?.clientOrderId + ? '#FF9C24' + : order.side == 'buy' + ? '#4BA53B' + : '#AA2222' + ) + .setQuantityBorderColor( + order.perpTrigger?.clientOrderId + ? '#FF9C24' + : order.side == 'buy' + ? '#4BA53B' + : '#AA2222' + ) + .setCancelButtonBorderColor( + order.perpTrigger?.clientOrderId + ? '#FF9C24' + : order.side == 'buy' + ? '#4BA53B' + : '#AA2222' + ) + .setBodyBackgroundColor( + theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832' + ) + .setQuantityBackgroundColor( + theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832' + ) + .setCancelButtonBackgroundColor( + theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832' + ) .setBodyFont('Lato, sans-serif') .setQuantityFont('Lato, sans-serif') - .setLineColor(order.perpTrigger?.clientOrderId ? '#FF9C24' : order.side == 'buy' ? '#4BA53B' : '#AA2222') + .setLineColor( + order.perpTrigger?.clientOrderId + ? '#FF9C24' + : order.side == 'buy' + ? '#4BA53B' + : '#AA2222' + ) .setLineLength(3) .setLineWidth(2) .setLineStyle(1) } - function getLineText(order, market) { if (order.perpTrigger?.clientOrderId) { - const triggerPrice = order.perpTrigger.triggerPrice * Math.pow(10, market.config.baseDecimals - market.config.quoteDecimals) + const triggerPrice = + order.perpTrigger.triggerPrice * + Math.pow(10, market.config.baseDecimals - market.config.quoteDecimals) if (order.side === 'buy') { if (order.perpTrigger.triggerCondition === 'above') { - return (order.orderType === 'market' ? `Stop Loss ` : `Stop Limit `) + `(${order.orderType} ${order.side}) if price is ${order.perpTrigger.triggerCondition} ${usdFormatter(triggerPrice)}` + return ( + (order.orderType === 'market' ? `Stop Loss ` : `Stop Limit `) + + `(${order.orderType} ${order.side}) if price is ${ + order.perpTrigger.triggerCondition + } ${usdFormatter(triggerPrice)}` + ) } else { - return `Take Profit (${order.orderType} ${order.side}) if price is ${order.perpTrigger.triggerCondition} ${usdFormatter(triggerPrice)}` + return `Take Profit (${order.orderType} ${order.side}) if price is ${ + order.perpTrigger.triggerCondition + } ${usdFormatter(triggerPrice)}` } } else { if (order.perpTrigger.triggerCondition === 'below') { - return (order.orderType === 'market' ? `Stop Loss ` : `Stop Limit `) + `(${order.orderType} ${order.side}) if price is ${order.perpTrigger.triggerCondition} ${usdFormatter(triggerPrice)}` + return ( + (order.orderType === 'market' ? `Stop Loss ` : `Stop Limit `) + + `(${order.orderType} ${order.side}) if price is ${ + order.perpTrigger.triggerCondition + } ${usdFormatter(triggerPrice)}` + ) } else { - return `Take Profit (${order.orderType} ${order.side}) if price is ${order.perpTrigger.triggerCondition} ${usdFormatter(triggerPrice)}` + return `Take Profit (${order.orderType} ${order.side}) if price is ${ + order.perpTrigger.triggerCondition + } ${usdFormatter(triggerPrice)}` } } } else { @@ -414,7 +459,6 @@ const TVChartContainer = () => { } } - function deleteLines() { tvWidgetRef.current.onChartReady(() => { if (lines?.size > 0) { @@ -426,7 +470,6 @@ const TVChartContainer = () => { return new Map() } - function drawLines() { const tempLines = new Map() tvWidgetRef.current.onChartReady(() => { @@ -439,10 +482,14 @@ const TVChartContainer = () => { return tempLines } - useInterval(() => { - if (selectedMarginAccount && connected && !moveInProgress && !orderInProgress && openOrders?.length > 0) { - + if ( + selectedMarginAccount && + connected && + !moveInProgress && + !orderInProgress && + openOrders?.length > 0 + ) { let matches = 0 let openOrdersInSelectedMarket = 0 lines?.forEach((value, key) => { @@ -460,23 +507,23 @@ const TVChartContainer = () => { }) if ( - lines?.size != openOrdersInSelectedMarket - || matches != openOrdersInSelectedMarket - || lines?.size > 0 && lines?.size != matches - || lines?.size > 0 && !selectedMarginAccount - || priceReset + lines?.size != openOrdersInSelectedMarket || + matches != openOrdersInSelectedMarket || + (lines?.size > 0 && lines?.size != matches) || + (lines?.size > 0 && !selectedMarginAccount) || + priceReset ) { - if (priceReset) { togglePriceReset(false) } + if (priceReset) { + togglePriceReset(false) + } setLines(deleteLines()) setLines(drawLines()) } - } else if (lines?.size > 0 && !moveInProgress && !orderInProgress) { setLines(deleteLines()) } }, [100]) - return
} diff --git a/components/trade_form/TradeType.jsx b/components/trade_form/TradeType.jsx index 44a11c16..47aca2b0 100644 --- a/components/trade_form/TradeType.jsx +++ b/components/trade_form/TradeType.jsx @@ -62,4 +62,4 @@ const TradeType = ({ ) } -export default TradeType \ No newline at end of file +export default TradeType diff --git a/package.json b/package.json index 9f7c55b7..c338723d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,8 @@ "dayjs": "^1.10.4", "immer": "^9.0.1", "immutable-tuple": "^0.4.10", + "intro.js": "^4.2.2", + "intro.js-react": "^0.5.0", "lodash-es": "^4.17.21", "next": "^11.1.0", "next-i18next": "^8.9.0", diff --git a/pages/_app.tsx b/pages/_app.tsx index 050462fa..8e7581f4 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -2,6 +2,7 @@ import Head from 'next/head' import { ThemeProvider } from 'next-themes' import '../node_modules/react-grid-layout/css/styles.css' import '../node_modules/react-resizable/css/styles.css' +import 'intro.js/introjs.css' import '../styles/index.css' import useWallet from '../hooks/useWallet' import useHydrateStore from '../hooks/useHydrateStore' diff --git a/pages/perp/[market].tsx b/pages/perp/[market].tsx index 781def3f..5d1105b8 100644 --- a/pages/perp/[market].tsx +++ b/pages/perp/[market].tsx @@ -13,6 +13,7 @@ import useLocalStorageState from '../../hooks/useLocalStorageState' import AlphaModal, { ALPHA_MODAL_KEY } from '../../components/AlphaModal' import { PageBodyWrapper } from '../../components/styles' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import IntroTips, { SHOW_TOUR_KEY } from '../../components/IntroTips' export async function getServerSideProps({ locale }) { return { @@ -25,8 +26,10 @@ export async function getServerSideProps({ locale }) { const PerpMarket = () => { const [alphaAccepted] = useLocalStorageState(ALPHA_MODAL_KEY, false) + const [showTour] = useLocalStorageState(SHOW_TOUR_KEY, false) const groupConfig = useMangoGroupConfig() const setMangoStore = useMangoStore((s) => s.set) + const connected = useMangoStore((s) => s.wallet.connected) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache) const marketConfig = useMangoStore((s) => s.selectedMarket.config) @@ -61,6 +64,7 @@ const PerpMarket = () => { return (
+ diff --git a/styles/index.css b/styles/index.css index b7759119..4d2173df 100644 --- a/styles/index.css +++ b/styles/index.css @@ -321,3 +321,79 @@ input[type='number'] { .react-swipeable-view-container > div[aria-hidden='true'] { height: 0; } + +/* Intro Tour */ + +.introjs-tooltip { + @apply bg-transparent; +} + +.introjs-arrow { + @apply border-0; +} + +.intro-tooltip * { + @apply bg-th-bkg-1 font-body text-th-fgd-1; +} + +.introjs-skipbutton { + @apply text-sm text-th-fgd-3; +} + +.introjs-tooltip-header { + @apply rounded-t-md; + padding: 10px; +} + +.introjs-tooltipbuttons { + @apply border-th-bkg-4 hidden font-bold rounded-b-md focus:outline-none; +} + +.introjs-nextbutton { + @apply bg-th-bkg-4 border-th-bkg-4 flex h-4 items-center rounded-full text-sm text-th-fgd-1; + text-shadow: none; +} + +.introjs-prevbutton { + @apply hidden; +} + +.introjs-button:hover { + @apply bg-th-bkg-4 border-th-bkg-4 brightness-[1.15] text-th-fgd-1; +} + +.introjs-button:focus { + @apply bg-th-bkg-4 border-th-bkg-4 shadow-none text-th-fgd-1; +} + +.introjs-hidden { + @apply hidden; +} + +.introjs-progressbar { + @apply bg-th-primary; +} + +.intro-tooltip h4 { + @apply font-body font-bold mb-1 text-lg; +} + +.intro-tooltip p { + @apply font-body text-sm text-th-fgd-3; +} + +.introjs-bullets ul li a { + @apply bg-th-bkg-4; +} + +.introjs-bullets ul li a:hover { + @apply bg-th-fgd-4; +} + +.introjs-bullets ul li a.active { + @apply bg-th-primary; +} + +.intro-highlight { + @apply border-0; +} diff --git a/utils/index.ts b/utils/index.ts index 97eadd0a..c461ed41 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -108,7 +108,7 @@ export function calculateTradePrice( if (tradeType === 'Market') { return calculateMarketPrice(orderBook, baseSize, side) } else if (TRIGGER_ORDER_TYPES.includes(tradeType)) { - if( tradeType === 'Take Profit Limit' || tradeType === 'Stop Limit' ) { + if (tradeType === 'Take Profit Limit' || tradeType === 'Stop Limit') { return Number(price) } else { return Number(triggerPrice) diff --git a/yarn.lock b/yarn.lock index 1861a9f7..1bad80af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4802,6 +4802,16 @@ internmap@^1.0.0: resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== +intro.js-react@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/intro.js-react/-/intro.js-react-0.5.0.tgz#d4e67a4f6e4726bee803222ff09d6deacb4598c8" + integrity sha512-tr9uhoy/aRgcdZxpqYC/TPSVlJ/Gt9Kx4mpI7clCdFa7c3SG8reJugIa1U5XnWlDLRNMc+e8egb8Jf186yvc1A== + +intro.js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/intro.js/-/intro.js-4.2.2.tgz#9077549cc6ef697e78d18d1c05003b7471281e1a" + integrity sha512-Zgz2e8syCuttJ2vJlDOWCSWPUJBr7AOJkU5Ti3zcvXho+y//q0ixwoT+PkPLJWI7AX35IdgRcxAEWUrOAJYiNQ== + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"