diff --git a/.github/workflows/console-test-run.yml b/.github/workflows/console-test-run.yml index 9943ab54c..27573fa84 100644 --- a/.github/workflows/console-test-run.yml +++ b/.github/workflows/console-test-run.yml @@ -205,7 +205,7 @@ jobs: # run tests #---------------------------------------------- - name: Run tests - run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 1 --dist loadfile --durations=45 + run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 4 --dist loadfile --durations=45 working-directory: apps/trading/e2e #---------------------------------------------- # upload traces diff --git a/README.md b/README.md index 9e657a93a..45ce92595 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V This repository is managed using [Nx](https://nx.dev). -# πŸ”Ž Applications in this repo +## πŸ”Ž Applications in this repo ### [Block explorer](./apps/explorer) @@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts. The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract. -# 🧱 Libraries in this repo +## 🧱 Libraries in this repo ### [UI toolkit](./libs/ui-toolkit) @@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve Generic react helpers that can be used across multiple applications, along with other utilities. -# πŸ’» Develop +## πŸ’» Develop ### Set up @@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more. -# πŸ‹ Hosting a console +## πŸ‹ Hosting a console To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions). @@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet ``` -# πŸ“‘ License +## πŸ“‘ License [MIT](./LICENSE) diff --git a/apps/explorer-e2e/src/integration/proposal.cy.js b/apps/explorer-e2e/src/integration/proposal.cy.js index ef03e28b3..3d1568141 100644 --- a/apps/explorer-e2e/src/integration/proposal.cy.js +++ b/apps/explorer-e2e/src/integration/proposal.cy.js @@ -24,10 +24,6 @@ context('Proposal page', { tags: '@smoke' }, function () { cy.get_element_by_col_id('title').should('have.text', proposalTitle); cy.get_element_by_col_id('type').should('have.text', 'NewMarket'); cy.get_element_by_col_id('state').should('have.text', 'Enacted'); - cy.getByTestId('vote-progress').should('be.visible'); - cy.getByTestId('vote-progress-bar-for') - .invoke('attr', 'style') - .should('eq', 'width: 100%;'); cy.get('[col-id="cDate"]') .invoke('text') .should('match', dateTimeRegex); @@ -73,10 +69,6 @@ context('Proposal page', { tags: '@smoke' }, function () { 'have.text', 'Waiting for Node Vote' ); - cy.getByTestId('vote-progress').should('be.visible'); - cy.getByTestId('vote-progress-bar-against') - .invoke('attr', 'style') - .should('eq', 'width: 100%;'); cy.get('[col-id="cDate"]') .invoke('text') .should('match', dateTimeRegex); diff --git a/apps/explorer/src/app/components/proposals/proposals-table.tsx b/apps/explorer/src/app/components/proposals/proposals-table.tsx index 88d31a697..6999008eb 100644 --- a/apps/explorer/src/app/components/proposals/proposals-table.tsx +++ b/apps/explorer/src/app/components/proposals/proposals-table.tsx @@ -1,5 +1,4 @@ import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals'; -import { VoteProgress } from '@vegaprotocol/proposals'; import { type AgGridReact } from 'ag-grid-react'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { AgGrid } from '@vegaprotocol/datagrid'; @@ -12,12 +11,7 @@ import { type ColDef } from 'ag-grid-community'; import type { RowClickedEvent } from 'ag-grid-community'; import { getDateTimeFormat } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; -import { - NetworkParams, - useNetworkParams, -} from '@vegaprotocol/network-parameters'; import { ProposalStateMapping } from '@vegaprotocol/types'; -import BigNumber from 'bignumber.js'; import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment'; import { BREAKPOINT_MD } from '../../config/breakpoints'; import { JsonViewerDialog } from '../dialogs/json-viewer-dialog'; @@ -31,15 +25,7 @@ type ProposalsTableProps = { data: ProposalListFieldsFragment[] | null; }; export const ProposalsTable = ({ data }: ProposalsTableProps) => { - const { params } = useNetworkParams([ - NetworkParams.governance_proposal_market_requiredMajority, - ]); const tokenLink = useLinks(DApp.Governance); - const requiredMajorityPercentage = useMemo(() => { - const requiredMajority = - params?.governance_proposal_market_requiredMajority ?? 1; - return new BigNumber(requiredMajority).times(100); - }, [params?.governance_proposal_market_requiredMajority]); const gridRef = useRef(null); useLayoutEffect(() => { @@ -90,33 +76,6 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => { return value ? ProposalStateMapping[value] : '-'; }, }, - { - colId: 'voting', - maxWidth: 100, - hide: window.innerWidth <= BREAKPOINT_MD, - headerName: t('Voting'), - cellRenderer: ({ - data, - }: VegaICellRendererParams) => { - if (data) { - const yesTokens = new BigNumber(data.votes.yes.totalTokens); - const noTokens = new BigNumber(data.votes.no.totalTokens); - const totalTokensVoted = yesTokens.plus(noTokens); - const yesPercentage = totalTokensVoted.isZero() - ? new BigNumber(0) - : yesTokens.multipliedBy(100).dividedBy(totalTokensVoted); - return ( -
- -
- ); - } - return '-'; - }, - }, { colId: 'cDate', maxWidth: 150, @@ -184,7 +143,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => { }, }, ], - [requiredMajorityPercentage, tokenLink] + [tokenLink] ); return ( <> diff --git a/apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx b/apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx new file mode 100644 index 000000000..c9bd63a2b --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx @@ -0,0 +1,46 @@ +import { t } from '@vegaprotocol/i18n'; +import { TxDetailsShared } from '../shared/tx-details-shared'; +import { TableWithTbody } from '../../../table'; +import type { components } from '../../../../../types/explorer'; + +import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response'; +import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response'; +import { TableCell, TableRow } from '../../../table'; + +type Update = components['schemas']['v1UpdatePartyProfile']; + +interface TxDetailsUpdatePartyProfileProps { + txData: BlockExplorerTransactionResult | undefined; + pubKey: string | undefined; + blockData: TendermintBlocksResponse | undefined; +} + +/** + * Party profiles can be an alias and arbitrary key/values pairs. + * This component displays the alias, if any, but not the metadata. When there is + * some wider usage, we can decide how to render it. For now, it's available in the + * full TX details. + */ +export const TxDetailsUpdatePartyProfile = ({ + txData, + pubKey, + blockData, +}: TxDetailsUpdatePartyProfileProps) => { + if (!txData?.command.updatePartyProfile) { + return <>{t('Awaiting Block Explorer transaction details')}; + } + + const update: Update = txData.command.updatePartyProfile; + + return ( + + + {update.alias && ( + + {t('New alias')} + {update.alias} + + )} + + ); +}; diff --git a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx index 9f7343700..937dcd8d1 100644 --- a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx @@ -34,6 +34,7 @@ import { TxDetailsUpdateReferralSet } from './tx-update-referral-set'; import { TxDetailsJoinTeam } from './tx-join-team'; import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode'; import { TxBatchProposal } from './tx-batch-proposal'; +import { TxDetailsUpdatePartyProfile } from './proposal/tx-update-party-profile'; interface TxDetailsWrapperProps { txData: BlockExplorerTransactionResult | undefined; @@ -139,6 +140,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) { return TxDetailsUpdateMarginMode; case 'Batch Proposal': return TxBatchProposal; + case 'Update Party Profile': + return TxDetailsUpdatePartyProfile; default: return TxDetailsGeneric; } diff --git a/apps/explorer/src/app/components/txs/tx-filter.tsx b/apps/explorer/src/app/components/txs/tx-filter.tsx index 290bc9625..96233834f 100644 --- a/apps/explorer/src/app/components/txs/tx-filter.tsx +++ b/apps/explorer/src/app/components/txs/tx-filter.tsx @@ -44,6 +44,7 @@ export type FilterOption = | 'Submit Order' | 'Transfer Funds' | 'Undelegate' + | 'Update Party Profile' | 'Update Referral Set' | 'Update Margin Mode' | 'Validator Heartbeat' @@ -79,6 +80,7 @@ export const filterOptions: Record = { 'Apply Referral Code', 'Create Referral Set', 'Join Team', + 'Update Party Profile', 'Update Referral Set', ], 'External Data': ['Chain Event', 'Submit Oracle Data'], 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/.env.mainnet b/apps/trading/.env.mainnet index afb43a660..a37ccdf44 100644 --- a/apps/trading/.env.mainnet +++ b/apps/trading/.env.mainnet @@ -1,4 +1,4 @@ -NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/af6a2d529a11f8158bc8ca2a +NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.rpc.grove.city/v1/af6a2d529a11f8158bc8ca2a NX_ETHERSCAN_URL=https://etherscan.io NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613 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/competitions/competitions-create-team.tsx b/apps/trading/client-pages/competitions/competitions-create-team.tsx index 21661fef3..19710ffef 100644 --- a/apps/trading/client-pages/competitions/competitions-create-team.tsx +++ b/apps/trading/client-pages/competitions/competitions-create-team.tsx @@ -1,5 +1,10 @@ -import { useSearchParams } from 'react-router-dom'; -import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit'; +import { Link, useSearchParams } from 'react-router-dom'; +import { + Intent, + TradingAnchorButton, + VegaIcon, + VegaIconNames, +} from '@vegaprotocol/ui-toolkit'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { useT } from '../../lib/use-t'; @@ -30,6 +35,19 @@ export const CompetitionsCreateTeam = () => {
+ + {' '} + + {t('Go back to the competitions')} + +

{isSolo ? t('Create solo team') : t('Create a team')}

@@ -78,15 +96,17 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {

{t('Team creation transaction successful')}

{code && ( <> -

- Your team ID is:{' '} - - {code} - -

+
+
{t('Your team ID:')}
+
+ + {code} + +
+
{ const t = useT(); @@ -29,6 +37,19 @@ export const CompetitionsUpdateTeam = () => {
+ + {' '} + + {t('Go back to the team profile')} + +

{t('Update a team')}

@@ -57,7 +78,8 @@ const UpdateTeamFormContainer = ({ pubKey: string; }) => { const t = useT(); - const { team, loading, error } = useTeam(teamId, pubKey); + const [refetching, setRefetching] = useState(false); + const { team, loading, error, refetch } = useTeam(teamId, pubKey); const { err, status, onSubmit } = useReferralSetTransaction({ onSuccess: () => { @@ -65,7 +87,15 @@ const UpdateTeamFormContainer = ({ }, }); - if (loading) { + // refetch when saved + useEffect(() => { + if (refetch && status === 'confirmed') { + refetch(); + setRefetching(true); + } + }, [refetch, status]); + + if (loading && !refetching) { return ; } if (error) { @@ -84,6 +114,33 @@ const UpdateTeamFormContainer = ({ return ; } + if (status === 'confirmed') { + return ( +
+

+ {' '} + {t('Changes successfully saved to your team.')} +

+ + + {t('View team')} + +
+ ); + } + const defaultValues: FormFields = { id: team.teamId, name: team.name, diff --git a/apps/trading/client-pages/market/market-header-stats.tsx b/apps/trading/client-pages/market/market-header-stats.tsx index e477a4e19..c9ee0dee6 100644 --- a/apps/trading/client-pages/market/market-header-stats.tsx +++ b/apps/trading/client-pages/market/market-header-stats.tsx @@ -19,6 +19,7 @@ import { useFundingRate, useMarketTradingMode, useExternalTwap, + getQuoteName, } from '@vegaprotocol/markets'; import { MarketState as State } from '@vegaprotocol/types'; import { HeaderStat } from '../../components/header'; @@ -41,6 +42,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const asset = getAsset(market); + const quoteUnit = getQuoteName(market); return ( <> @@ -54,12 +56,15 @@ 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/client-pages/markets/market-list-table.tsx b/apps/trading/client-pages/markets/market-list-table.tsx index 7f2881498..0b7476c0f 100644 --- a/apps/trading/client-pages/markets/market-list-table.tsx +++ b/apps/trading/client-pages/markets/market-list-table.tsx @@ -5,7 +5,7 @@ import { useDataGridEvents, } from '@vegaprotocol/datagrid'; import type { MarketMaybeWithData } from '@vegaprotocol/markets'; -import { useColumnDefs } from './use-column-defs'; +import { useMarketsColumnDefs } from './use-column-defs'; import type { DataGridStore } from '../../stores/datagrid-store-slice'; import { type StateCreator, create } from 'zustand'; import { persist } from 'zustand/middleware'; @@ -50,7 +50,7 @@ export const useMarketsStore = create()( ); export const MarketListTable = (props: Props) => { - const columnDefs = useColumnDefs(); + const columnDefs = useMarketsColumnDefs(); const gridStore = useMarketsStore((store) => store.gridStore); const updateGridStore = useMarketsStore((store) => store.updateGridStore); diff --git a/apps/trading/client-pages/markets/mobile-buttons.tsx b/apps/trading/client-pages/markets/mobile-buttons.tsx new file mode 100644 index 000000000..4e3118235 --- /dev/null +++ b/apps/trading/client-pages/markets/mobile-buttons.tsx @@ -0,0 +1,235 @@ +import { Route, Routes } from 'react-router-dom'; +import { + Intent, + MobileActionsDropdown, + Tooltip, + TradingButton, + TradingDropdownItem, + VegaIcon, + VegaIconNames, +} from '@vegaprotocol/ui-toolkit'; +import { type BarView, ViewType, useSidebar } from '../../components/sidebar'; +import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; +import { useT } from '../../lib/use-t'; +import { useScreenDimensions } from '@vegaprotocol/react-helpers'; +import { useEffect } from 'react'; +import classNames from 'classnames'; +import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; + +const ViewInitializer = () => { + const currentRouteId = useGetCurrentRouteId(); + const { setViews, getView } = useSidebar(); + const view = getView(currentRouteId); + const { screenSize } = useScreenDimensions(); + const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize); + useEffect(() => { + if (largeScreen && view === undefined) { + setViews({ type: ViewType.Order }, currentRouteId); + } + }, [setViews, view, currentRouteId, largeScreen]); + return null; +}; + +export const MarketsMobileSidebar = () => { + const t = useT(); + const currentRouteId = useGetCurrentRouteId(); + const { pubKeys, isReadOnly } = useVegaWallet(); + const openVegaWalletDialog = useVegaWalletDialogStore( + (store) => store.openVegaWalletDialog + ); + + return ( + + + +
+ {!pubKeys || isReadOnly ? ( + <> + { + openVegaWalletDialog(); + }} + > + {t('Connect')} + + + + + ) : ( + <> + + + + + )} +
+ + } + /> +
+ ); +}; + +export const MobileButton = ({ + view, + tooltip: label, + disabled = false, + onClick, + routeId, +}: { + view?: ViewType; + tooltip: string; + disabled?: boolean; + onClick?: () => void; + routeId: string; +}) => { + const { setViews, getView } = useSidebar((store) => ({ + setViews: store.setViews, + getView: store.getView, + })); + const currView = getView(routeId); + const onSelect = (view: BarView['type']) => { + if (view === currView?.type) { + setViews(null, routeId); + } else { + setViews({ type: view }, routeId); + } + }; + + const buttonClasses = classNames( + 'flex items-center p-1 rounded', + 'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500', + { + 'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500': + !view || view !== currView?.type, + 'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black': + view && view === currView?.type, + } + ); + + return ( + + onSelect(view as BarView['type']))} + disabled={disabled} + > + {label} + + + ); +}; + +export const MobileDropdownItem = ({ + view, + icon, + tooltip, + disabled = false, + onClick, + routeId, +}: { + view?: ViewType; + icon: VegaIconNames; + tooltip: string; + disabled?: boolean; + onClick?: () => void; + routeId: string; +}) => { + const { setViews, getView } = useSidebar((store) => ({ + setViews: store.setViews, + getView: store.getView, + })); + const currView = getView(routeId); + const onSelect = (view: BarView['type']) => { + if (view === currView?.type) { + setViews(null, routeId); + } else { + setViews({ type: view }, routeId); + } + }; + + const buttonClasses = classNames( + 'flex items-center p-1 rounded', + 'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500', + { + 'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500': + !view || view !== currView?.type, + 'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black': + view && view === currView?.type, + } + ); + + return ( + + onSelect(view as BarView['type']))} + disabled={disabled} + > + + {tooltip} + + + ); +}; + +export const MobileBarActionsDropdown = ({ + currentRouteId, +}: { + currentRouteId: string; +}) => { + const t = useT(); + return ( + + + + + + + + ); +}; diff --git a/apps/trading/client-pages/markets/use-column-defs.tsx b/apps/trading/client-pages/markets/use-column-defs.tsx index 06b55a43e..5617b2833 100644 --- a/apps/trading/client-pages/markets/use-column-defs.tsx +++ b/apps/trading/client-pages/markets/use-column-defs.tsx @@ -7,21 +7,31 @@ import type { } from '@vegaprotocol/datagrid'; import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid'; import * as Schema from '@vegaprotocol/types'; -import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils'; +import { + addDecimalsFormatNumber, + formatNumber, + toBigNum, +} from '@vegaprotocol/utils'; import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import type { + MarketFieldsFragment, MarketMaybeWithData, MarketMaybeWithDataAndCandles, } from '@vegaprotocol/markets'; import { MarketActionsDropdown } from './market-table-actions'; -import { calcCandleVolume, getAsset } from '@vegaprotocol/markets'; +import { + calcCandleVolume, + calcCandleVolumePrice, + getAsset, + getQuoteName, +} from '@vegaprotocol/markets'; import { MarketCodeCell } from './market-code-cell'; import { useT } from '../../lib/use-t'; const { MarketTradingMode, AuctionTrigger } = Schema; -export const useColumnDefs = () => { +export const useMarketsColumnDefs = () => { const t = useT(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); return useMemo( @@ -158,11 +168,25 @@ export const useColumnDefs = () => { }: ValueFormatterParams) => { const candles = data?.candles; const vol = candles ? calcCandleVolume(candles) : '0'; + const quoteName = getQuoteName(data as MarketFieldsFragment); + const volPrice = + candles && + calcCandleVolumePrice( + candles, + data.decimalPlaces, + data.positionDecimalPlaces + ); + const volume = data && vol && vol !== '0' ? addDecimalsFormatNumber(vol, data.positionDecimalPlaces) : '0.00'; - return volume; + const volumePrice = + volPrice && formatNumber(volPrice, data?.decimalPlaces); + + return volumePrice + ? `${volume} (${volumePrice} ${quoteName})` + : volume; }, }, { diff --git a/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx b/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx index dc390c6eb..14461ebe3 100644 --- a/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx +++ b/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx @@ -2,6 +2,7 @@ import { VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { SidebarButton, ViewType } from '../../components/sidebar'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useT } from '../../lib/use-t'; +import { MobileButton } from '../markets/mobile-buttons'; export const PortfolioSidebar = () => { const t = useT(); @@ -30,3 +31,28 @@ export const PortfolioSidebar = () => { ); }; + +export const PortfolioMobileSidebar = () => { + const t = useT(); + const currentRouteId = useGetCurrentRouteId(); + + return ( +
+ + + +
+ ); +}; diff --git a/apps/trading/client-pages/referrals/hooks/use-referral-program.ts b/apps/trading/client-pages/referrals/hooks/use-referral-program.ts index e530d6f05..dffb01a2c 100644 --- a/apps/trading/client-pages/referrals/hooks/use-referral-program.ts +++ b/apps/trading/client-pages/referrals/hooks/use-referral-program.ts @@ -1,4 +1,4 @@ -import { getNumberFormat } from '@vegaprotocol/utils'; +import { formatNumber } from '@vegaprotocol/utils'; import sortBy from 'lodash/sortBy'; import omit from 'lodash/omit'; import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram'; @@ -107,9 +107,7 @@ export const useReferralProgram = () => { discountFactor: Number(t.referralDiscountFactor), discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%', minimumVolume: Number(t.minimumRunningNotionalTakerVolume), - volume: getNumberFormat(0).format( - Number(t.minimumRunningNotionalTakerVolume) - ), + volume: formatNumber(t.minimumRunningNotionalTakerVolume, 0), epochs: Number(t.minimumEpochs), }; }); diff --git a/apps/trading/client-pages/referrals/referral-statistics.tsx b/apps/trading/client-pages/referrals/referral-statistics.tsx index 245871d8e..38cc15b5b 100644 --- a/apps/trading/client-pages/referrals/referral-statistics.tsx +++ b/apps/trading/client-pages/referrals/referral-statistics.tsx @@ -14,9 +14,9 @@ import { import { useVegaWallet } from '@vegaprotocol/wallet'; import { addDecimalsFormatNumber, + formatNumber, getDateFormat, getDateTimeFormat, - getNumberFormat, getUserLocale, removePaginationWrapper, } from '@vegaprotocol/utils'; @@ -323,7 +323,7 @@ export const Statistics = ({ } description={} > - {getNumberFormat(0).format(Number(totalCommissionValue))} + {formatNumber(totalCommissionValue, 0)} ); @@ -563,8 +563,8 @@ export const RefereesTable = ({ ) .map((r) => ({ ...r, - volume: getNumberFormat(0).format(r.volume), - commission: getNumberFormat(0).format(r.commission), + volume: formatNumber(r.volume, 0), + commission: formatNumber(r.commission, 0), })) .reverse()} /> diff --git a/apps/trading/components/competitions/competitions-leaderboard.tsx b/apps/trading/components/competitions/competitions-leaderboard.tsx index cc1cfc25b..5a3291cc3 100644 --- a/apps/trading/components/competitions/competitions-leaderboard.tsx +++ b/apps/trading/components/competitions/competitions-leaderboard.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; import { Splash } from '@vegaprotocol/ui-toolkit'; -import { getNumberFormat } from '@vegaprotocol/utils'; +import { formatNumber } from '@vegaprotocol/utils'; import { type useTeams } from '../../lib/hooks/use-teams'; import { useT } from '../../lib/use-t'; import { Table } from '../table'; @@ -15,8 +15,7 @@ export const CompetitionsLeaderboard = ({ }) => { const t = useT(); - const num = (n?: number | string) => - !n ? '-' : getNumberFormat(0).format(Number(n)); + const num = (n?: number | string) => (!n ? '-' : formatNumber(n, 0)); if (!data || data.length === 0) { return {t('Could not find any teams')}; @@ -33,9 +32,9 @@ export const CompetitionsLeaderboard = ({ { name: 'status', displayName: t('Status') }, { name: 'volume', displayName: t('Volume') }, ]} - data={data.map((td, i) => { + data={data.map((td) => { // leaderboard place or medal - let rank: number | React.ReactNode = i + 1; + let rank: number | React.ReactNode = td.rank; if (rank === 1) rank = ; if (rank === 2) rank = ; if (rank === 3) rank = ; diff --git a/apps/trading/components/competitions/games-container.tsx b/apps/trading/components/competitions/games-container.tsx index e59f9c576..9d6053942 100644 --- a/apps/trading/components/competitions/games-container.tsx +++ b/apps/trading/components/competitions/games-container.tsx @@ -1,6 +1,11 @@ import { type TransferNode } from '@vegaprotocol/types'; -import { ActiveRewardCard } from '../rewards-container/active-rewards'; +import { + ActiveRewardCard, + isActiveReward, +} from '../rewards-container/active-rewards'; import { useT } from '../../lib/use-t'; +import { useAssetsMapProvider } from '@vegaprotocol/assets'; +import { useMarketsMapProvider } from '@vegaprotocol/markets'; export const GamesContainer = ({ data, @@ -10,8 +15,35 @@ export const GamesContainer = ({ currentEpoch: number; }) => { const t = useT(); + // Re-load markets and assets in the games container to ensure that the + // the cards are updated (not grayed out) when the user navigates to the games page + const { data: assets } = useAssetsMapProvider(); + const { data: markets } = useMarketsMapProvider(); - if (!data || data.length === 0) { + const enrichedTransfers = data + .filter((node) => isActiveReward(node, currentEpoch)) + .map((node) => { + if (node.transfer.kind.__typename !== 'RecurringTransfer') { + return node; + } + + const asset = + assets && + assets[ + node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || '' + ]; + + const marketsInScope = + node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map( + (id) => markets && markets[id] + ); + + return { ...node, asset, markets: marketsInScope }; + }); + + if (!enrichedTransfers || !enrichedTransfers.length) return null; + + if (!enrichedTransfers || enrichedTransfers.length === 0) { return (

{t('There are currently no games available.')} @@ -21,7 +53,7 @@ export const GamesContainer = ({ return (

- {data.map((game, i) => { + {enrichedTransfers.map((game, i) => { // TODO: Remove `kind` prop from ActiveRewardCard const { transfer } = game; if ( diff --git a/apps/trading/components/competitions/team-avatar.tsx b/apps/trading/components/competitions/team-avatar.tsx index 7e8be0ecb..2b50a51ba 100644 --- a/apps/trading/components/competitions/team-avatar.tsx +++ b/apps/trading/components/competitions/team-avatar.tsx @@ -1,4 +1,6 @@ +import { isValidUrl } from '@vegaprotocol/utils'; import classNames from 'classnames'; +import { useEffect, useState } from 'react'; const NUM_AVATARS = 20; const AVATAR_PATHNAME_PATTERN = '/team-avatars/{id}.png'; @@ -11,6 +13,26 @@ export const getFallbackAvatar = (teamId: string) => { return AVATAR_PATHNAME_PATTERN.replace('{id}', avatarId); }; +const useAvatar = (teamId: string, url: string) => { + const fallback = getFallbackAvatar(teamId); + const [avatar, setAvatar] = useState(fallback); + + useEffect(() => { + if (!isValidUrl(url)) return; + fetch(url, { cache: 'force-cache' }) + .then((response) => { + if (response.ok) { + setAvatar(url); + } + }) + .catch(() => { + /** noop */ + }); + }); + + return avatar; +}; + export const TeamAvatar = ({ teamId, imgUrl, @@ -22,7 +44,7 @@ export const TeamAvatar = ({ alt?: string; size?: 'large' | 'small'; }) => { - const img = imgUrl && imgUrl.length > 0 ? imgUrl : getFallbackAvatar(teamId); + const img = useAvatar(teamId, imgUrl); return ( // eslint-disable-next-line @next/next/no-img-element
{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/components/rewards-container/active-rewards.tsx b/apps/trading/components/rewards-container/active-rewards.tsx index 232efa97f..03ea7c171 100644 --- a/apps/trading/components/rewards-container/active-rewards.tsx +++ b/apps/trading/components/rewards-container/active-rewards.tsx @@ -468,7 +468,7 @@ export const ActiveRewardCard = ({ }
{dispatchStrategy?.dispatchMetric && ( - + {t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])} )} diff --git a/apps/trading/components/settings/settings.tsx b/apps/trading/components/settings/settings.tsx index 66b1999a0..4648906a4 100644 --- a/apps/trading/components/settings/settings.tsx +++ b/apps/trading/components/settings/settings.tsx @@ -81,6 +81,7 @@ export const Settings = () => { intent={Intent.Primary} onClick={() => { localStorage.clear(); + sessionStorage.clear(); window.location.reload(); }} > diff --git a/apps/trading/components/sidebar/sidebar.tsx b/apps/trading/components/sidebar/sidebar.tsx index b54854ae0..628e574d6 100644 --- a/apps/trading/components/sidebar/sidebar.tsx +++ b/apps/trading/components/sidebar/sidebar.tsx @@ -17,6 +17,7 @@ import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useT } from '../../lib/use-t'; import { ErrorBoundary } from '../error-boundary'; +import { useScreenDimensions } from '@vegaprotocol/react-helpers'; export enum ViewType { Order = 'Order', @@ -26,9 +27,10 @@ export enum ViewType { Transfer = 'Transfer', Settings = 'Settings', ViewAs = 'ViewAs', + Close = 'Close', } -type SidebarView = +export type BarView = | { type: ViewType.Deposit; assetId?: string; @@ -49,6 +51,9 @@ type SidebarView = } | { type: ViewType.Settings; + } + | { + type: ViewType.Close; }; export const Sidebar = ({ options }: { options?: ReactNode }) => { @@ -57,26 +62,52 @@ export const Sidebar = ({ options }: { options?: ReactNode }) => { const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1'; const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen); const { pubKeys } = useVegaWallet(); + const { isMobile } = useScreenDimensions(); + const { getView } = useSidebar((store) => ({ + setViews: store.setViews, + getView: store.getView, + })); + const currView = getView(currentRouteId); return ( -
- {options && } -