From 76c07992d3422fa9ffc757625317812eb71461e0 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:24:48 +0200 Subject: [PATCH] feat(trading): update mobile layout (#5718) Co-authored-by: Matthew Russell --- apps/explorer/src/assets/manifest.json | 4 +- apps/governance/src/assets/manifest.json | 4 +- apps/static/src/index.html | 2 +- apps/trading/assets/manifest.json | 16 ++- .../market/market-header-stats.tsx | 3 +- .../client-pages/market/trade-panels.tsx | 113 +++++++++------ .../layouts/layout-with-sidebar.tsx | 12 +- .../trading/components/market-header/index.ts | 1 + .../market-header/mobile-market-header.tsx | 133 ++++++++++++++++++ apps/trading/components/navbar/index.tsx | 1 - apps/trading/components/navbar/nav-header.tsx | 81 ----------- apps/trading/components/navbar/navbar.tsx | 10 +- apps/trading/pages/_app.page.tsx | 15 +- apps/trading/pages/_document.page.tsx | 7 +- apps/trading/pages/client-router.tsx | 11 +- apps/trading/public/manifest.json | 22 +++ .../last-24h-price-change.tsx | 7 +- 17 files changed, 267 insertions(+), 175 deletions(-) create mode 100644 apps/trading/components/market-header/mobile-market-header.tsx delete mode 100644 apps/trading/components/navbar/nav-header.tsx create mode 100644 apps/trading/public/manifest.json diff --git a/apps/explorer/src/assets/manifest.json b/apps/explorer/src/assets/manifest.json index 949569331..4cbc76d39 100644 --- a/apps/explorer/src/assets/manifest.json +++ b/apps/explorer/src/assets/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Mainnet Stats", - "name": "Vega Mainnet statistics", + "short_name": "Explorer VEGA", + "name": "Vega Protocol - Explorer", "icons": [ { "src": "favicon.ico", diff --git a/apps/governance/src/assets/manifest.json b/apps/governance/src/assets/manifest.json index 949569331..4779dbc73 100644 --- a/apps/governance/src/assets/manifest.json +++ b/apps/governance/src/assets/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Mainnet Stats", - "name": "Vega Mainnet statistics", + "short_name": "Governance VEGA", + "name": "Vega Protocol - Governance", "icons": [ { "src": "favicon.ico", diff --git a/apps/static/src/index.html b/apps/static/src/index.html index 2ab0a6f71..b49517237 100644 --- a/apps/static/src/index.html +++ b/apps/static/src/index.html @@ -4,7 +4,7 @@ - Vega Protocol static asseets + Vega Protocol static assets diff --git a/apps/trading/assets/manifest.json b/apps/trading/assets/manifest.json index 949569331..37981d3d3 100644 --- a/apps/trading/assets/manifest.json +++ b/apps/trading/assets/manifest.json @@ -1,6 +1,12 @@ { - "short_name": "Mainnet Stats", - "name": "Vega Mainnet statistics", + "name": "Vega Protocol - Trading", + "short_name": "Console", + "description": "Vega Protocol - Trading dApp", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "theme_color": "#000000", + "background_color": "#ffffff", "icons": [ { "src": "favicon.ico", @@ -12,9 +18,5 @@ "type": "image/png", "sizes": "192x192" } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + ] } diff --git a/apps/trading/client-pages/market/market-header-stats.tsx b/apps/trading/client-pages/market/market-header-stats.tsx index 38952d49e..c9ee0dee6 100644 --- a/apps/trading/client-pages/market/market-header-stats.tsx +++ b/apps/trading/client-pages/market/market-header-stats.tsx @@ -56,6 +56,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { -} /> @@ -112,7 +113,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { heading={`${t('Funding Rate')} / ${t('Countdown')}`} testId="market-funding" > -
+
diff --git a/apps/trading/client-pages/market/trade-panels.tsx b/apps/trading/client-pages/market/trade-panels.tsx index 870a87509..759a56364 100644 --- a/apps/trading/client-pages/market/trade-panels.tsx +++ b/apps/trading/client-pages/market/trade-panels.tsx @@ -3,7 +3,6 @@ import { type Market } from '@vegaprotocol/markets'; // TODO: handle oracle banner // import { OracleBanner } from '@vegaprotocol/markets'; import { useState } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; import classNames from 'classnames'; import { Popover, @@ -12,21 +11,21 @@ import { VegaIconNames, } from '@vegaprotocol/ui-toolkit'; import { useT } from '../../lib/use-t'; -import { MarketBanner } from '../../components/market-banner'; import { ErrorBoundary } from '../../components/error-boundary'; import { type TradingView } from './trade-views'; import { TradingViews } from './trade-views'; - interface TradePanelsProps { market: Market; pinnedAsset?: PinnedAsset; } export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { - const [view, setView] = useState('chart'); - const viewCfg = TradingViews[view]; + const [topView, setTopView] = useState('chart'); + const topViewCfg = TradingViews[topView]; + const [bottomView, setBottomView] = useState('positions'); + const bottomViewCfg = TradingViews[bottomView]; - const renderView = () => { + const renderView = (view: TradingView) => { const Component = TradingViews[view].component; if (!Component) { @@ -39,12 +38,13 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { // so watch out for clashes in props return ( - ; + ); }; - const renderMenu = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const renderMenu = (viewCfg: any) => { if ('menu' in viewCfg || 'settings' in viewCfg) { return (
@@ -69,55 +69,80 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { }; return ( -
-
- -
-
{renderMenu()}
-
- - {({ width, height }) => ( -
- {renderView()} -
- )} -
-
-
- {Object.keys(TradingViews) - // filter to control available views for the current market - // eg only perps should get the funding views - .filter((_key) => { - const key = _key as TradingView; - const perpOnlyViews = ['funding', 'fundingPayments']; +
+
+
+ {['chart', 'orderbook', 'trades', 'liquidity', 'fundingPayments'] + // filter to control available views for the current market + // e.g. only perpetuals should get the funding views + .filter((_key) => { + const key = _key as TradingView; + const perpOnlyViews = ['funding', 'fundingPayments']; + + if ( + market?.tradableInstrument.instrument.product.__typename === + 'Perpetual' + ) { + return true; + } + + if (perpOnlyViews.includes(key)) { + return false; + } - if ( - market?.tradableInstrument.instrument.product.__typename === - 'Perpetual' - ) { return true; - } + }) + .map((_key) => { + const key = _key as TradingView; + const isActive = topView === key; + return ( + { + setTopView(key); + }} + /> + ); + })} +
+
+
{renderMenu(topViewCfg)}
+
{renderView(topView)}
+
+
- if (perpOnlyViews.includes(key)) { - return false; - } - - return true; - }) - .map((_key) => { +
+
+ {[ + 'positions', + 'activeOrders', + 'closedOrders', + 'rejectedOrders', + 'orders', + 'stopOrders', + 'collateral', + 'fills', + ].map((_key) => { const key = _key as TradingView; - const isActive = view === key; + const isActive = bottomView === key; return ( { - setView(key); + setBottomView(key); }} /> ); })} +
+
+
{renderMenu(bottomViewCfg)}
+
{renderView(bottomView)}
+
); @@ -157,7 +182,7 @@ const useViewLabel = (view: TradingView) => { depth: t('Depth'), liquidity: t('Liquidity'), funding: t('Funding'), - fundingPayments: t('Funding Payments'), + fundingPayments: t('Funding'), orderbook: t('Orderbook'), trades: t('Trades'), positions: t('Positions'), diff --git a/apps/trading/components/layouts/layout-with-sidebar.tsx b/apps/trading/components/layouts/layout-with-sidebar.tsx index eec996706..937113c62 100644 --- a/apps/trading/components/layouts/layout-with-sidebar.tsx +++ b/apps/trading/components/layouts/layout-with-sidebar.tsx @@ -3,7 +3,6 @@ import { Outlet } from 'react-router-dom'; import { Sidebar, SidebarContent, useSidebar } from '../sidebar'; import classNames from 'classnames'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; - export const LayoutWithSidebar = ({ header, sidebar, @@ -27,10 +26,13 @@ export const LayoutWithSidebar = ({
{header}
diff --git a/apps/trading/components/market-header/index.ts b/apps/trading/components/market-header/index.ts index fe42898da..54e3fbc3e 100644 --- a/apps/trading/components/market-header/index.ts +++ b/apps/trading/components/market-header/index.ts @@ -1 +1,2 @@ export * from './market-header'; +export * from './mobile-market-header'; diff --git a/apps/trading/components/market-header/mobile-market-header.tsx b/apps/trading/components/market-header/mobile-market-header.tsx new file mode 100644 index 000000000..3e2438882 --- /dev/null +++ b/apps/trading/components/market-header/mobile-market-header.tsx @@ -0,0 +1,133 @@ +import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; +import { MarketSelector } from '../market-selector'; +import { + Last24hPriceChange, + useMarket, + useMarketList, +} from '@vegaprotocol/markets'; +import { useParams } from 'react-router-dom'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; +import { useState } from 'react'; +import { useT } from '../../lib/use-t'; +import classNames from 'classnames'; +import { MarketHeaderStats } from '../../client-pages/market/market-header-stats'; +import { MarketMarkPrice } from '../market-mark-price'; +/** + * This is only rendered for the mobile navigation + */ +export const MobileMarketHeader = () => { + const t = useT(); + const { marketId } = useParams(); + const { data } = useMarket(marketId); + const [openMarket, setOpenMarket] = useState(false); + const [openPrice, setOpenPrice] = useState(false); + + // Ensure that markets are kept cached so opening the list + // shows all markets instantly + useMarketList(); + + if (!marketId) return null; + + return ( +
+ { + setOpenMarket(x); + }} + trigger={ +

+ {data + ? data.tradableInstrument.instrument.code + : t('Select market')} + + + +

+ } + > + setOpenMarket(false)} + /> +
+ { + setOpenPrice(x); + }} + trigger={ + + {data && ( + <> + + + + + + + + + )} + + } + > + {data && ( +
+ +
+ )} +
+
+ ); +}; + +export interface PopoverProps extends PopoverPrimitive.PopoverProps { + trigger: React.ReactNode | string; +} + +export const FullScreenPopover = ({ + trigger, + children, + open, + onOpenChange, +}: PopoverProps) => { + return ( + + + {trigger} + + + + {children} + + + + ); +}; diff --git a/apps/trading/components/navbar/index.tsx b/apps/trading/components/navbar/index.tsx index 376d1c7c5..f5899d036 100644 --- a/apps/trading/components/navbar/index.tsx +++ b/apps/trading/components/navbar/index.tsx @@ -1,2 +1 @@ export * from './navbar'; -export * from './nav-header'; diff --git a/apps/trading/components/navbar/nav-header.tsx b/apps/trading/components/navbar/nav-header.tsx deleted file mode 100644 index 0715900a7..000000000 --- a/apps/trading/components/navbar/nav-header.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; -import { MarketSelector } from '../market-selector'; -import { useMarket, useMarketList } from '@vegaprotocol/markets'; -import { useParams } from 'react-router-dom'; -import * as PopoverPrimitive from '@radix-ui/react-popover'; -import { useState } from 'react'; -import { useT } from '../../lib/use-t'; -import classNames from 'classnames'; - -/** - * This is only rendered for the mobile navigation - */ -export const NavHeader = () => { - const t = useT(); - const { marketId } = useParams(); - const { data } = useMarket(marketId); - const [open, setOpen] = useState(false); - - // Ensure that markets are kept cached so opening the list - // shows all markets instantly - useMarketList(); - - if (!marketId) return null; - - return ( - { - setOpen(x); - }} - trigger={ -

- {data ? data.tradableInstrument.instrument.code : t('Select market')} - - - -

- } - > - setOpen(false)} - /> -
- ); -}; - -export interface PopoverProps extends PopoverPrimitive.PopoverProps { - trigger: React.ReactNode | string; -} - -export const FullScreenPopover = ({ - trigger, - children, - open, - onOpenChange, -}: PopoverProps) => { - return ( - - - {trigger} - - - - {children} - - - - ); -}; diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index b304df2d4..476e5465f 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -34,13 +34,7 @@ import { supportedLngs } from '../../lib/i18n'; type MenuState = 'wallet' | 'nav' | null; type Theme = 'system' | 'yellow'; -export const Navbar = ({ - children, - theme = 'system', -}: { - children?: ReactNode; - theme?: Theme; -}) => { +export const Navbar = ({ theme = 'system' }: { theme?: Theme }) => { const i18n = useI18n(); const t = useT(); // menu state for small screens @@ -75,8 +69,6 @@ export const Navbar = ({ > - {/* Left section */} -
{children}
{/* Used to show header in nav on mobile */}
setMenu(null)} /> diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index e2273fc90..e6913f4d2 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -14,7 +14,7 @@ import './styles.css'; import { usePageTitleStore } from '../stores'; import DialogsContainer from './dialogs-container'; import ToastsManager from './toasts-manager'; -import { HashRouter, useLocation, Route, Routes } from 'react-router-dom'; +import { HashRouter, useLocation } from 'react-router-dom'; import { Bootstrapper } from '../components/bootstrapper'; import { AnnouncementBanner } from '../components/banner'; import { Navbar } from '../components/navbar'; @@ -25,9 +25,7 @@ import { ProtocolUpgradeProposalNotification, } from '@vegaprotocol/proposals'; import { ViewingBanner } from '../components/viewing-banner'; -import { NavHeader } from '../components/navbar/nav-header'; import { Telemetry } from '../components/telemetry'; -import { Routes as AppRoutes } from '../lib/links'; import { SSRLoader } from './ssr-loader'; import { PartyActiveOrdersHandler } from './party-active-orders-handler'; import { MaybeConnectEagerly } from './maybe-connect-eagerly'; @@ -73,16 +71,7 @@ function AppBody({ Component }: AppProps) { <div className={gridClasses}> <AnnouncementBanner /> - <Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'}> - <Routes> - <Route - path={AppRoutes.MARKETS} - // render nothing for markets/all, otherwise markets/:marketId will match with markets/all - element={null} - /> - <Route path={AppRoutes.MARKET} element={<NavHeader />} /> - </Routes> - </Navbar> + <Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} /> <div data-testid="banners"> <ProtocolUpgradeProposalNotification mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING} diff --git a/apps/trading/pages/_document.page.tsx b/apps/trading/pages/_document.page.tsx index 80e28f715..4ddd0fa58 100644 --- a/apps/trading/pages/_document.page.tsx +++ b/apps/trading/pages/_document.page.tsx @@ -24,11 +24,14 @@ export default function Document() { {/* scripts */} <script src="/theme-setter.js" type="text/javascript" async /> + + {/* manifest */} + <link rel="manifest" href="/apps/trading/public/manifest.json" /> </Head> <Html> <body - // Nextjs will set body to display none until js runs. Because the entire app is client rendered - // and delivered via ipfs we override this to show a server side render loading animation until the + // Next.js will set body to display none until js runs. Because the entire app is client rendered + // and delivered via IPFS we override this to show a server side render loading animation until the // js is downloaded and react takes over rendering style={{ display: 'block' }} className="bg-white dark:bg-vega-cdark-900 text-default font-alpha" diff --git a/apps/trading/pages/client-router.tsx b/apps/trading/pages/client-router.tsx index 832e9a7cc..5db2ad224 100644 --- a/apps/trading/pages/client-router.tsx +++ b/apps/trading/pages/client-router.tsx @@ -23,7 +23,7 @@ import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-bo import { compact } from 'lodash'; import { useFeatureFlags } from '@vegaprotocol/environment'; import { LiquidityHeader } from '../components/liquidity-header'; -import { MarketHeader } from '../components/market-header'; +import { MarketHeader, MobileMarketHeader } from '../components/market-header'; import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar'; import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar'; import { MarketsSidebar } from '../client-pages/markets/markets-sidebar'; @@ -33,6 +33,7 @@ import { CompetitionsTeams } from '../client-pages/competitions/competitions-tea import { CompetitionsTeam } from '../client-pages/competitions/competitions-team'; import { CompetitionsCreateTeam } from '../client-pages/competitions/competitions-create-team'; import { CompetitionsUpdateTeam } from '../client-pages/competitions/competitions-update-team'; +import { useScreenDimensions } from '@vegaprotocol/react-helpers'; // These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM // Using dynamic imports is a workaround for this until pennant is published as ESM @@ -50,6 +51,9 @@ const NotFound = () => { export const useRouterConfig = (): RouteObject[] => { const featureFlags = useFeatureFlags((state) => state.flags); + const { screenSize } = useScreenDimensions(); + const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize); + const marketHeader = largeScreen ? <MarketHeader /> : <MobileMarketHeader />; const routeConfig = compact([ { index: true, @@ -151,10 +155,7 @@ export const useRouterConfig = (): RouteObject[] => { { path: 'markets/*', element: ( - <LayoutWithSidebar - header={<MarketHeader />} - sidebar={<MarketsSidebar />} - /> + <LayoutWithSidebar header={marketHeader} sidebar={<MarketsSidebar />} /> ), children: [ { diff --git a/apps/trading/public/manifest.json b/apps/trading/public/manifest.json new file mode 100644 index 000000000..77abf4cc7 --- /dev/null +++ b/apps/trading/public/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "Vega Protocol - Trading", + "short_name": "Console", + "description": "Vega Protocol - Trading dApp", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "theme_color": "#000000", + "background_color": "#ffffff", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "cover.png", + "type": "image/png", + "sizes": "192x192" + } + ] +} diff --git a/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx b/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx index 4a1afafab..828841bf4 100644 --- a/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx +++ b/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx @@ -18,12 +18,15 @@ interface Props { initialValue?: string[]; isHeader?: boolean; noUpdate?: boolean; + // render prop for no price change + fallback?: React.ReactNode; } export const Last24hPriceChange = ({ marketId, decimalPlaces, initialValue, + fallback, }: Props) => { const t = useT(); const { oneDayCandles, error, fiveDaysCandles } = useCandles({ @@ -48,13 +51,13 @@ export const Last24hPriceChange = ({ </span> } > - <span>-</span> + <span>{fallback}</span> </Tooltip> ); } if (error || !isNumeric(decimalPlaces)) { - return <span>-</span>; + return <span>{fallback}</span>; } const candles = oneDayCandles?.map((c) => c.close) || initialValue || [];