Compare commits

..
Author SHA1 Message Date
Bartłomiej Głownia cfe083cbee feat(trading): margin mode selector 2024-01-05 13:54:53 +01:00
Ben 462066959d chore(trading): update vega (#5563) 2024-01-03 12:00:00 +00:00
daro-maj f12f5ab961 chore(trading): fix volume issue for market selector tests (#5564) 2024-01-03 12:04:49 +01:00
98b2460d8b fix(trading): mobile friendly referral and fees views (#5530)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-01-02 15:50:04 +00:00
Matthew Russell 67c182f3de chore(trading-view): minor tv chart tweaks (#5545) 2024-01-02 13:20:01 +00:00
68e0009ab0 feat(trading,markets,proposals): market banners (#5494)
Co-authored-by: bwallacee <ben@vega.xyz>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-02 11:04:43 +00:00
Bartłomiej Głownia ad74b12908 feat(trading): do not show error message if there is no faucet tx error (#5539) 2024-01-02 10:19:10 +01:00
Matthew Russell 7fff33e493 feat(trading): competition feature flag and team page (#5549) 2023-12-29 15:50:51 +00:00
daro-maj e7cb2c8276 chore(trading): fix fees tests (#5548) 2023-12-29 15:25:33 +00:00
4adaeea40a feat(trading): pane settings view (#5509)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-12-28 12:33:18 +00:00
Matthew Russell d47d894147 fix(deal-ticket): stray curly in stop order deal ticket (#5547) 2023-12-28 12:27:56 +00:00
Bartłomiej Głownia 515932d401 fix(trading): use correct interpolation prefix in market info label (#5537) 2023-12-27 18:47:19 +00:00
98 changed files with 1252 additions and 456 deletions
@@ -24,7 +24,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
const navigate = useNavigate();
const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible(
ref.current?.api.setColumnsVisible(
['id', 'type', 'status'],
window.innerWidth > BREAKPOINT_MD
);
@@ -29,7 +29,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => {
const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible(
gridRef.current?.api.setColumnsVisible(
['id', 'state', 'asset'],
window.innerWidth > BREAKPOINT_MD
);
@@ -44,11 +44,11 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => {
const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible(
gridRef.current?.api.setColumnsVisible(
['voting', 'cDate', 'eDate', 'type'],
window.innerWidth > BREAKPOINT_MD
);
gridRef.current?.columnApi.setColumnWidth(
gridRef.current?.api.setColumnWidth(
'actions',
window.innerWidth > BREAKPOINT_MD ? 221 : 80
);
+1
View File
@@ -25,3 +25,4 @@ NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TEAM_COMPETITION=true
@@ -77,7 +77,11 @@ const MainGrid = memo(
{market &&
market.tradableInstrument.instrument.product.__typename ===
'Perpetual' ? (
<Tab id="funding-payments" name={t('Funding payments')}>
<Tab
id="funding-payments"
name={t('Funding payments')}
settings={<TradingViews.fundingPayments.settings />}
>
<ErrorBoundary feature="funding-payments">
<TradingViews.fundingPayments.component
marketId={market.id}
@@ -99,7 +103,11 @@ const MainGrid = memo(
<TradingViews.orderbook.component marketId={market.id} />
</ErrorBoundary>
</Tab>
<Tab id="trades" name={t('Trades')}>
<Tab
id="trades"
name={t('Trades')}
settings={<TradingViews.trades.settings />}
>
<ErrorBoundary feature="trades">
<TradingViews.trades.component marketId={market.id} />
</ErrorBoundary>
@@ -120,6 +128,7 @@ const MainGrid = memo(
id="positions"
name={t('Positions')}
menu={<TradingViews.positions.menu />}
settings={<TradingViews.positions.settings />}
>
<ErrorBoundary feature="positions">
<TradingViews.positions.component />
@@ -129,17 +138,26 @@ const MainGrid = memo(
id="open-orders"
name={t('Open')}
menu={<TradingViews.activeOrders.menu />}
settings={<TradingViews.activeOrders.settings />}
>
<ErrorBoundary feature="activeOrders">
<TradingViews.activeOrders.component />
</ErrorBoundary>
</Tab>
<Tab id="closed-orders" name={t('Closed')}>
<Tab
id="closed-orders"
name={t('Closed')}
settings={<TradingViews.closedOrders.settings />}
>
<ErrorBoundary feature="closedOrders">
<TradingViews.closedOrders.component />
</ErrorBoundary>
</Tab>
<Tab id="rejected-orders" name={t('Rejected')}>
<Tab
id="rejected-orders"
name={t('Rejected')}
settings={<TradingViews.rejectedOrders.settings />}
>
<ErrorBoundary feature="rejectedOrders">
<TradingViews.rejectedOrders.component />
</ErrorBoundary>
@@ -148,25 +166,35 @@ const MainGrid = memo(
id="orders"
name={t('All')}
menu={<TradingViews.orders.menu />}
settings={<TradingViews.orders.settings />}
>
<ErrorBoundary feature="orders">
<TradingViews.orders.component />
</ErrorBoundary>
</Tab>
{featureFlags.STOP_ORDERS ? (
<Tab id="stop-orders" name={t('Stop orders')}>
<Tab
id="stop-orders"
name={t('Stop orders')}
settings={<TradingViews.stopOrders.settings />}
>
<ErrorBoundary feature="stop-orders">
<TradingViews.stopOrders.component />
</ErrorBoundary>
</Tab>
) : null}
<Tab id="fills" name={t('Fills')}>
<Tab
id="fills"
name={t('Fills')}
settings={<TradingViews.fills.settings />}
>
<TradingViews.fills.component />
</Tab>
<Tab
id="accounts"
name={t('Collateral')}
menu={<TradingViews.collateral.menu />}
settings={<TradingViews.collateral.settings />}
>
<ErrorBoundary feature="collateral">
<TradingViews.collateral.component
@@ -5,7 +5,12 @@ import { type Market } from '@vegaprotocol/markets';
import { useState } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import classNames from 'classnames';
import { Splash } from '@vegaprotocol/ui-toolkit';
import {
Popover,
Splash,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import { MarketBanner } from '../../components/market-banner';
import { ErrorBoundary } from '../../components/error-boundary';
@@ -19,6 +24,7 @@ interface TradePanelsProps {
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('chart');
const viewCfg = TradingViews[view];
const renderView = () => {
const Component = TradingViews[view].component;
@@ -39,19 +45,27 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
};
const renderMenu = () => {
const viewCfg = TradingViews[view];
if ('menu' in viewCfg) {
const Menu = viewCfg.menu;
if ('menu' in viewCfg || 'settings' in viewCfg) {
return (
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu />
{'menu' in viewCfg ? <viewCfg.menu /> : null}
{'settings' in viewCfg ? (
<Popover
align="end"
trigger={
<span className="ml-1 flex items-center justify-center h-6 w-6">
<VegaIcon name={VegaIconNames.COG} size={16} />
</span>
}
>
<div className="p-4 flex justify-end">
<viewCfg.settings />
</div>
</Popover>
) : null}
</div>
);
}
return null;
};
return (
@@ -60,7 +74,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
<MarketBanner market={market} />
</div>
<div>{renderMenu()}</div>
<div className="h-full">
<div className="h-full relative">
<AutoSizer>
{({ width, height }) => (
<div style={{ width, height }} className="overflow-auto">
@@ -98,7 +112,9 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
key={key}
view={key}
isActive={isActive}
onClick={() => setView(key)}
onClick={() => {
setView(key);
}}
/>
);
})}
@@ -1,15 +1,36 @@
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
import { TradesContainer } from '../../components/trades-container';
import {
TradesContainer,
TradesSettings,
} from '../../components/trades-container';
import { OrderbookContainer } from '../../components/orderbook-container';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { AccountsContainer } from '../../components/accounts-container';
import {
FillsContainer,
FillsSettings,
} from '../../components/fills-container';
import {
PositionsContainer,
PositionsSettings,
} from '../../components/positions-container';
import {
AccountsContainer,
AccountsSettings,
} from '../../components/accounts-container';
import { LiquidityContainer } from '../../components/liquidity-container';
import { FundingContainer } from '../../components/funding-container';
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container';
import {
FundingPaymentsContainer,
FundingPaymentsSettings,
} from '../../components/funding-payments-container';
import {
OrdersContainer,
OrdersSettings,
} from '../../components/orders-container';
import {
StopOrdersContainer,
StopOrdersSettings,
} from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu';
import { PositionsMenu } from '../../components/positions-menu';
import { ChartContainer, ChartMenu } from '../../components/chart-container';
@@ -32,37 +53,46 @@ export const TradingViews = {
},
fundingPayments: {
component: FundingPaymentsContainer,
settings: FundingPaymentsSettings,
},
orderbook: {
component: OrderbookContainer,
},
trades: {
component: TradesContainer,
settings: TradesSettings,
},
positions: {
component: PositionsContainer,
menu: PositionsMenu,
settings: PositionsSettings,
},
activeOrders: {
component: () => <OrdersContainer filter={Filter.Open} />,
menu: OpenOrdersMenu,
settings: () => <OrdersSettings filter={Filter.Open} />,
},
closedOrders: {
component: () => <OrdersContainer filter={Filter.Closed} />,
settings: () => <OrdersSettings filter={Filter.Closed} />,
},
rejectedOrders: {
component: () => <OrdersContainer filter={Filter.Rejected} />,
settings: () => <OrdersSettings filter={Filter.Rejected} />,
},
orders: {
component: OrdersContainer,
menu: OpenOrdersMenu,
settings: OrdersSettings,
},
stopOrders: {
component: StopOrdersContainer,
settings: StopOrdersSettings,
},
collateral: {
component: AccountsContainer,
menu: AccountsMenu,
settings: AccountsSettings,
},
fills: { component: FillsContainer },
fills: { component: FillsContainer, settings: FillsSettings },
} as const;
@@ -42,7 +42,7 @@ export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
},
});
const useMarketsStore = create<DataGridSlice>()(
export const useMarketsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_market_list_store',
})
@@ -16,6 +16,7 @@ import {
} from '@vegaprotocol/environment';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
import { MarketsSettings } from './markets-settings';
export const MarketsPage = () => {
const t = useT();
@@ -34,7 +35,11 @@ export const MarketsPage = () => {
<div className="h-full pt-0.5 pb-3 px-1.5">
<div className="h-full my-1 border rounded-sm border-default">
<Tabs storageKey="console-markets">
<Tab id="open-markets" name={t('Open markets')}>
<Tab
id="open-markets"
name={t('Open markets')}
settings={<MarketsSettings />}
>
<ErrorBoundary feature="markets-open">
<OpenMarkets />
</ErrorBoundary>
@@ -42,6 +47,7 @@ export const MarketsPage = () => {
<Tab
id="proposed-markets"
name={t('Proposed markets')}
settings={<MarketsSettings />}
menu={
<TradingAnchorButton
size="extra-small"
@@ -56,7 +62,11 @@ export const MarketsPage = () => {
<Proposed />
</ErrorBoundary>
</Tab>
<Tab id="closed-markets" name={t('Closed markets')}>
<Tab
id="closed-markets"
name={t('Closed markets')}
settings={<MarketsSettings />}
>
<ErrorBoundary feature="markets-closed">
<Closed />
</ErrorBoundary>
@@ -0,0 +1,8 @@
import { GridSettings } from '../../components/grid-settings/grid-settings';
import { useMarketsStore } from './market-list-table';
export const MarketsSettings = () => (
<GridSettings
updateGridStore={useMarketsStore((store) => store.updateGridStore)}
/>
);
@@ -5,14 +5,29 @@ import { titlefy } from '@vegaprotocol/utils';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import { AccountsContainer } from '../../components/accounts-container';
import {
AccountsContainer,
AccountsSettings,
} from '../../components/accounts-container';
import { DepositsContainer } from '../../components/deposits-container';
import { FillsContainer } from '../../components/fills-container';
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
import { PositionsContainer } from '../../components/positions-container';
import {
FillsContainer,
FillsSettings,
} from '../../components/fills-container';
import {
FundingPaymentsContainer,
FundingPaymentsSettings,
} from '../../components/funding-payments-container';
import {
PositionsContainer,
PositionsSettings,
} from '../../components/positions-container';
import { PositionsMenu } from '../../components/positions-menu';
import { WithdrawalsContainer } from '../../components/withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import {
OrdersContainer,
OrdersSettings,
} from '../../components/orders-container';
import { LedgerContainer } from '../../components/ledger-container';
import {
ResizableGrid,
@@ -76,22 +91,27 @@ export const Portfolio = () => {
id="positions"
name={t('Positions')}
menu={<PositionsMenu />}
settings={<PositionsSettings />}
>
<ErrorBoundary feature="portfolio-positions">
<PositionsContainer allKeys />
</ErrorBoundary>
</Tab>
<Tab id="orders" name={t('Orders')}>
<Tab id="orders" name={t('Orders')} settings={<OrdersSettings />}>
<ErrorBoundary feature="portfolio-orders">
<OrdersContainer />
</ErrorBoundary>
</Tab>
<Tab id="fills" name={t('Fills')}>
<Tab id="fills" name={t('Fills')} settings={<FillsSettings />}>
<ErrorBoundary feature="portfolio-fills">
<FillsContainer />
</ErrorBoundary>
</Tab>
<Tab id="funding-payments" name={t('Funding payments')}>
<Tab
id="funding-payments"
name={t('Funding payments')}
settings={<FundingPaymentsSettings />}
>
<ErrorBoundary feature="portfolio-funding-payments">
<FundingPaymentsContainer />
</ErrorBoundary>
@@ -114,6 +134,7 @@ export const Portfolio = () => {
<Tab
id="collateral"
name={t('Collateral')}
settings={<AccountsSettings />}
menu={<AccountsMenu />}
>
<ErrorBoundary feature="portfolio-accounts">
@@ -296,7 +296,7 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
<>
<div
data-testid="referral-apply-code-form"
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto w-2/3 max-w-md rounded-lg p-8"
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto md:w-2/3 max-w-md rounded-lg p-8"
>
<h3 className="calt mb-4 text-center text-2xl">
{t('Apply a referral code')}
@@ -3,7 +3,7 @@ export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const SKY_BACKGROUND =
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[37%_0px] bg-[length:1440px] bg-no-repeat bg-local';
// TODO: Update the links to use the correct referral related pages
export const REFERRAL_DOCS_LINK =
@@ -49,7 +49,7 @@ export const CreateCodeForm = () => {
return (
<div
data-testid="referral-create-code-form"
className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
className="md:w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
>
<h3 className="mb-4 text-2xl text-center calt">
{t('Create a referral code')}
@@ -1,5 +1,5 @@
import { useT } from '../../lib/use-t';
import { Table } from './table';
import { Table } from '../../components/table';
export const HowItWorksTable = () => {
const t = useT();
@@ -5,16 +5,16 @@ import { useT } from '../../lib/use-t';
export const LandingBanner = () => {
const t = useT();
return (
<div className={classNames('relative mb-20')}>
<div className={classNames('relative mb-10 lg:mb-20')}>
<div className="">
<div
aria-hidden
className="absolute top-20 right-[120px] md:right-[240px] max-sm:hidden"
className="absolute top-20 right-[220px] md:right-[240px] max-sm:hidden"
>
<AnimatedDudeWithWire />
</div>
<div className="pt-20 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10">
<div className="pt-10 lg:pt-20 sm:w-[50%]">
<h1 className="text-3xl _text-[6vw] lg:!text-6xl leading-[1em] font-alpha calt mb-10">
{t('Vega community referrals')}
</h1>
<p className="text-lg mb-1">
@@ -22,7 +22,7 @@ export const LandingBanner = () => {
'Referral programs can be proposed and created via community governance.'
)}
</p>
<p className="text-lg mb-10">
<p className="text-lg mb-1">
{t(
'Once live, users can generate referral codes to share with their friends and earn commission on their trades, while referred traders can access fee discounts based on the running volume of the group.'
)}
@@ -13,7 +13,7 @@ export const Layout = ({
<div
className={classNames(
'max-w-[1440px]',
'mx-auto px-16 md:px-32 pb-32',
'mx-auto px-4 lg:px-32 pb-32',
'relative z-0',
className
)}
@@ -14,7 +14,7 @@ import {
useUpdateReferees,
} from './hooks/use-referral';
import classNames from 'classnames';
import { Table } from './table';
import { Table } from '../../components/table';
import {
addDecimalsFormatNumber,
getDateFormat,
@@ -72,7 +72,7 @@ export const Referrals = () => {
{showNav && <Nav />}
<div
className={classNames({
'py-16': showNav,
'py-8 lg:py-16': showNav,
'h-[300px] relative': loading || error,
})}
>
+24 -41
View File
@@ -3,11 +3,12 @@ import {
getDateTimeFormat,
} from '@vegaprotocol/utils';
import { useReferralProgram } from './hooks/use-referral-program';
import { Table } from './table';
import { Table } from '../../components/table';
import classNames from 'classnames';
import { BORDER_COLOR, GRADIENT } from './constants';
import { Tag } from './tag';
import type { ComponentProps, ReactNode } from 'react';
import { Tag } from '../../components/helpers/tag';
import { getTierColor, getTierGradient } from '../../components/helpers/tiers';
import type { ReactNode } from 'react';
import { ExternalLink, truncateMiddle } from '@vegaprotocol/ui-toolkit';
import {
DApp,
@@ -19,25 +20,6 @@ import {
import { useT, ns } from '../../lib/use-t';
import { Trans } from 'react-i18next';
// rainbow-ish order
const TIER_COLORS: Array<ComponentProps<typeof Tag>['color']> = [
'pink',
'orange',
'yellow',
'green',
'blue',
'purple',
];
const getTierColor = (tier: number) => {
const tiers = Object.keys(TIER_COLORS).length;
let index = Math.abs(tier - 1);
if (tier >= tiers) {
index = index % tiers;
}
return TIER_COLORS[index];
};
const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
<div
className={classNames(
@@ -53,10 +35,12 @@ const StakingTier = ({
tier,
referralRewardMultiplier,
minimumStakedTokens,
max,
}: {
tier: number;
referralRewardMultiplier: string;
minimumStakedTokens: string;
max?: number;
}) => {
const t = useT();
const minimum = addDecimalsFormatNumber(minimumStakedTokens, 18);
@@ -97,7 +81,7 @@ const StakingTier = ({
)}
>
<div>
<Tag color={getTierColor(tier)}>
<Tag color={getTierColor(tier, max)}>
{t('Multiplier')} {referralRewardMultiplier}x
</Tag>
<p className="mt-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
@@ -159,7 +143,9 @@ export const TiersContainer = () => {
return (
<>
<h2 className="text-3xl mt-10">{t('Current program details')}</h2>
<h2 className="text-3xl mt-10 font-alpha calt">
{t('Current program details')}
</h2>
{details?.id && (
<p>
<Trans
@@ -198,7 +184,18 @@ export const TiersContainer = () => {
</div>
{/* Container */}
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20">
<div
className={classNames(
'md:bg-vega-clight-800',
'md:dark:bg-vega-cdark-800',
'md:text-black',
'md:dark:text-white',
'md:rounded-lg',
'md:p-6',
'mt-1',
'mb-20'
)}
>
{/* Benefit tiers */}
<div className="flex flex-col mb-5">
<h3 className="text-2xl calt">{t('Benefit tiers')}</h3>
@@ -261,6 +258,7 @@ const StakingTiers = ({
<StakingTier
key={i}
tier={tier}
max={data.length}
referralRewardMultiplier={referralRewardMultiplier}
minimumStakedTokens={minimumStakedTokens}
/>
@@ -322,22 +320,7 @@ const TiersTable = ({
className="bg-white dark:bg-vega-cdark-900"
data={data.map((d) => ({
...d,
className: classNames({
'from-vega-yellow-400 dark:from-vega-yellow-600 to-20% bg-highlight':
'yellow' === getTierColor(d.tier),
'from-vega-green-400 dark:from-vega-green-600 to-20% bg-highlight':
'green' === getTierColor(d.tier),
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
'blue' === getTierColor(d.tier),
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
'purple' === getTierColor(d.tier),
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
'pink' === getTierColor(d.tier),
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
'orange' === getTierColor(d.tier),
'from-vega-clight-200 dark:from-vega-cdark-200 to-20% bg-highlight':
'none' === getTierColor(d.tier),
}),
className: classNames(getTierGradient(d.tier, data.length)),
}))}
/>
);
+10 -5
View File
@@ -19,9 +19,11 @@ export const Tile = ({
return (
<div
className={classNames(
'rounded-lg overflow-hidden relative',
'bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white',
'p-6',
'text-black dark:text-white',
'overflow-hidden relative',
'p-3 md:p-6',
'rounded-lg',
'bg-vega-clight-800 dark:bg-vega-cdark-800',
className
)}
>
@@ -55,7 +57,10 @@ export const StatTile = ({
>
{title}
</h3>
<div data-testid={`${testId}-value`} className="text-5xl text-left">
<div
data-testid={`${testId}-value`}
className="text-2xl lg:text-5xl text-left"
>
{children}
</div>
{description && (
@@ -119,7 +124,7 @@ export const CodeTile = ({
>
<div
className={classNames(
'relative bg-rainbow bg-clip-text text-transparent text-5xl overflow-hidden',
'relative bg-rainbow bg-clip-text text-transparent text-2xl lg:text-5xl overflow-hidden',
FADE_OUT_STYLE
)}
>
+1
View File
@@ -0,0 +1 @@
export { Teams } from './teams';
@@ -0,0 +1,7 @@
export const Teams = () => {
return (
<div>
<h1>Teams</h1>
</div>
);
};
@@ -73,7 +73,7 @@ export const AccountsContainer = ({
);
};
const useAccountStore = create<DataGridSlice>()(
export const useAccountStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_accounts_store',
})
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useAccountStore } from './accounts-container';
export const AccountsSettings = () => (
<GridSettings
updateGridStore={useAccountStore((store) => store.updateGridStore)}
/>
);
@@ -1 +1,2 @@
export * from './accounts-container';
export * from './accounts-settings';
+17 -3
View File
@@ -9,6 +9,7 @@ export const Card = ({
loading = false,
highlight = false,
testId,
noBackgroundOnMobile = false,
}: {
children: ReactNode;
title: string;
@@ -16,20 +17,33 @@ export const Card = ({
loading?: boolean;
highlight?: boolean;
testId?: string;
noBackgroundOnMobile?: boolean;
}) => {
return (
<div
data-testid={testId}
className={classNames(
'bg-vega-clight-800 dark:bg-vega-cdark-800 col-span-full p-0.5 lg:col-auto',
'rounded-lg',
'col-span-full lg:col-auto',
{
'rounded-lg bg-vega-clight-800 dark:bg-vega-cdark-800 p-0.5':
!noBackgroundOnMobile,
'mt-3 md:mt-0 md:rounded-lg md:bg-vega-clight-800 md:dark:bg-vega-cdark-800 md:p-0.5':
noBackgroundOnMobile,
},
{
'bg-rainbow': highlight,
},
className
)}
>
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded p-4">
<div
className={classNames('h-full w-full', {
'bg-vega-clight-800 dark:bg-vega-cdark-800 rounded p-4':
!noBackgroundOnMobile,
'md:bg-vega-clight-800 md:dark:bg-vega-cdark-800 md:rounded md:p-4':
noBackgroundOnMobile,
})}
>
<h2 className="mb-3">{title}</h2>
{loading ? <CardLoader /> : children}
</div>
@@ -13,7 +13,7 @@ import { MarketFees } from './market-fees';
import { useVolumeStats } from './use-volume-stats';
import { useReferralStats } from './use-referral-stats';
import { formatPercentage, getAdjustedFee } from './utils';
import { Table, Td, Th, THead, Tr } from './table';
import { Table as SimpleTable } from '../../components/table';
import BigNumber from 'bignumber.js';
import { Links } from '../../lib/links';
import { Link } from 'react-router-dom';
@@ -24,6 +24,8 @@ import {
truncateMiddle,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import classNames from 'classnames';
import { getTierGradient } from '../helpers/tiers';
export const FeesContainer = () => {
const t = useT();
@@ -166,6 +168,7 @@ export const FeesContainer = () => {
className="lg:col-span-full xl:col-span-2"
loading={loading}
data-testid="volume-discount-card"
noBackgroundOnMobile={true}
>
<VolumeTiers
tiers={volumeTiers}
@@ -179,12 +182,14 @@ export const FeesContainer = () => {
className="lg:col-span-full xl:col-span-2"
loading={loading}
data-testid="referral-discount-card"
noBackgroundOnMobile={true}
>
<ReferralTiers
tiers={referralTiers}
tierIndex={referralTierIndex}
epochsInSet={epochsInSet}
referralVolumeInWindow={referralVolumeInWindow}
referralDiscountWindowLength={referralDiscountWindowLength}
/>
</Card>
<Card
@@ -192,6 +197,7 @@ export const FeesContainer = () => {
className="lg:col-span-full"
loading={marketsLoading}
data-testid="fees-by-market-card"
noBackgroundOnMobile={true}
>
<MarketFees
markets={markets}
@@ -477,44 +483,65 @@ const VolumeTiers = ({
return (
<div>
<Table>
<THead>
<Tr>
<Th data-testid="tier-header">{t('Tier')}</Th>
<Th data-testid="discount-header">{t('Discount')}</Th>
<Th data-testid="min-volume-header">{t('Min. trading volume')}</Th>
<Th data-testid="my-volume-header">
{t('myVolume', 'My volume (last {{count}} epochs)', {
count: windowLength,
})}
</Th>
<Th data-testid="actions-header" />
</Tr>
</THead>
<tbody>
{Array.from(tiers).map((tier, i) => {
const isUserTier = tierIndex === i;
return (
<Tr key={i} data-testid={`tier-row-${i}`}>
<Td data-testid={`tier-value-${i}`}>{i + 1}</Td>
<Td data-testid={`discount-value-${i}`}>
{formatPercentage(Number(tier.volumeDiscountFactor))}%
</Td>
<Td data-testid={`min-volume-value-${i}`}>
{formatNumber(tier.minimumRunningNotionalTakerVolume)}
</Td>
<Td data-testid={`my-volume-value-${i}`}>
{isUserTier ? formatNumber(lastEpochVolume) : ''}
</Td>
<Td data-testid={`your-tier-${i}`}>
{isUserTier ? <YourTier /> : null}
</Td>
</Tr>
);
})}
</tbody>
</Table>
<SimpleTable
className="bg-white dark:bg-vega-cdark-900"
columns={[
{ name: 'tier', displayName: t('Tier'), testId: 'col-tier-value' },
{
name: 'discount',
displayName: t('Discount'),
testId: 'discount-value',
},
{
name: 'minTradingVolume',
displayName: t('Min. trading volume'),
testId: 'min-volume-value',
},
{
name: 'myVolume',
displayName: t('myVolume', 'My volume (last {{count}} epochs)', {
count: windowLength,
}),
testId: 'my-volume-value',
},
{
name: 'indicator',
className: 'max-md:hidden',
testId: 'your-tier',
},
]}
data={Array.from(tiers).map((tier, i) => {
const isUserTier = tierIndex === i;
const indicator = isUserTier ? (
<YourTier testId={`your-volume-tier-${i}`} />
) : null;
const tierIndicator = (
<div className="flex justify-between">
<span data-testid={`tier-value-${i}`}>{i + 1}</span>
<span className="md:hidden">{indicator}</span>
</div>
);
return {
tier: tierIndicator,
discount: (
<>{formatPercentage(Number(tier.volumeDiscountFactor))}%</>
),
minTradingVolume: (
<>{formatNumber(tier.minimumRunningNotionalTakerVolume)}</>
),
myVolume: isUserTier ? (
formatNumber(lastEpochVolume)
) : (
<span className="md:hidden">-</span>
),
indicator: indicator,
className: classNames(
getTierGradient(i + 1, tiers.length),
'text-xs'
),
};
})}
/>
</div>
);
};
@@ -524,6 +551,7 @@ const ReferralTiers = ({
tierIndex,
epochsInSet,
referralVolumeInWindow,
referralDiscountWindowLength,
}: {
tiers: Array<{
referralDiscountFactor: string;
@@ -533,6 +561,7 @@ const ReferralTiers = ({
tierIndex: number;
epochsInSet: number;
referralVolumeInWindow: number;
referralDiscountWindowLength: number;
}) => {
const t = useT();
@@ -544,60 +573,81 @@ const ReferralTiers = ({
return (
<div>
<Table>
<THead>
<Tr>
<Th data-testid="tier-header">{t('Tier')}</Th>
<Th data-testid="discount-header">{t('Discount')}</Th>
<Th data-testid="min-volume-header">{t('Min. trading volume')}</Th>
<Th data-testid="required-epochs-header">{t('Required epochs')}</Th>
<Th data-testid="extra-header" />
</Tr>
</THead>
<tbody>
{Array.from(tiers).map((tier, i) => {
const isUserTier = tierIndex === i;
<SimpleTable
className="bg-white dark:bg-vega-cdark-900"
columns={[
{ name: 'tier', displayName: t('Tier'), testId: 'col-tier-value' },
{
name: 'discount',
displayName: t('Discount'),
tooltip: t(
"The proportion of the referee's taker fees to be discounted"
),
testId: 'discount-value',
},
{
name: 'volume',
displayName: t(
'minTradingVolume',
'Min. trading volume (last {{count}} epochs)',
{
count: referralDiscountWindowLength,
}
),
tooltip: t(
'The minimum running notional for the given benefit tier'
),
testId: 'min-volume-value',
},
{
name: 'epochs',
displayName: t('Min. epochs'),
tooltip: t(
'The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit'
),
testId: 'required-epochs-value',
},
{
name: 'indicator',
className: 'max-md:hidden',
testId: 'user-tier-or-unlocks',
},
]}
data={Array.from(tiers).map((tier, i) => {
const isUserTier = tierIndex === i;
const requiredVolume = Number(tier.minimumRunningNotionalTakerVolume);
const requiredVolume = Number(
tier.minimumRunningNotionalTakerVolume
);
let unlocksIn = null;
const indicator = isUserTier ? (
<YourTier testId={`your-referral-tier-${i}`} />
) : referralVolumeInWindow >= requiredVolume &&
epochsInSet < tier.minimumEpochs ? (
<span className="text-muted text-xs">
Unlocks in {tier.minimumEpochs - epochsInSet} epochs
</span>
) : null;
if (
referralVolumeInWindow >= requiredVolume &&
epochsInSet < tier.minimumEpochs
) {
unlocksIn = (
<span className="text-muted">
Unlocks in {tier.minimumEpochs - epochsInSet} epochs
</span>
);
}
const tierIndicator = (
<div className="flex justify-between">
<span data-testid={`tier-value-${i}`}>{i + 1}</span>
<span className="md:hidden">{indicator}</span>
</div>
);
return (
<Tr key={i} data-testid={`tier-row-${i}`}>
<Td data-testid={`tier-value-${i}`}>{i + 1}</Td>
<Td data-testid={`discount-value-${i}`}>
{formatPercentage(Number(tier.referralDiscountFactor))}%
</Td>
<Td data-testid={`min-volume-value-${i}`}>
{formatNumber(tier.minimumRunningNotionalTakerVolume)}
</Td>
<Td data-testid={`required-epochs-value-${i}`}>
{tier.minimumEpochs}
</Td>
<Td data-testid={`user-tier-or-unlocks-${i}`}>
{isUserTier ? (
<YourTier testId={`your-tier-${i}`} />
) : (
unlocksIn
)}
</Td>
</Tr>
);
})}
</tbody>
</Table>
return {
tier: tierIndicator,
discount: (
<>{formatPercentage(Number(tier.referralDiscountFactor))}%</>
),
volume: formatNumber(tier.minimumRunningNotionalTakerVolume),
epochs: tier.minimumEpochs,
indicator,
className: classNames(
getTierGradient(i + 1, tiers.length),
'text-xs'
),
};
})}
/>
</div>
);
};
@@ -611,7 +661,7 @@ const YourTier = ({ testId }: YourTierProps) => {
return (
<span
className="bg-rainbow whitespace-nowrap rounded-xl px-4 py-1.5 text-white"
className="bg-rainbow whitespace-nowrap rounded-xl px-4 py-1.5 text-white text-xs"
data-testid={testId}
>
{t('Your tier')}
@@ -8,43 +8,53 @@ import { useNavigateWithMeta } from '../../lib/hooks/use-market-click-handler';
import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t';
import { useMemo } from 'react';
import { type ColDef } from 'ag-grid-community/dist/lib/entities/colDef';
const useFeesTableColumnDefs = () => {
const useFeesTableColumnDefs = (): ColDef[] => {
const t = useT();
return useMemo(
() => [
{ field: 'code', cellRenderer: 'MarketCodeCell' },
{
field: 'feeAfterDiscount',
headerName: t('Total fee after discount'),
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'infraFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'makerFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'liquidityFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'totalFee',
headerName: t('Total fee before discount'),
valueFormatter: ({ value }: { value: number }) => value + '%',
},
],
() =>
[
{
field: 'code',
cellRenderer: 'MarketCodeCell',
pinned: 'left',
width: 150,
},
{
field: 'liquidityFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'feeAfterDiscount',
headerName: t('Total fee after discount'),
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'totalFee',
headerName: t('Total fee before discount'),
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'infraFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'makerFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
] as ColDef[],
[t]
);
};
const feesTableDefaultColDef = {
flex: 1,
minWidth: 62,
resizable: true,
sortable: true,
suppressMovable: true,
pinned: false,
};
const components = {
@@ -62,6 +72,8 @@ export const MarketFees = ({
}) => {
const navigateWithMeta = useNavigateWithMeta();
const colDef = useFeesTableColumnDefs();
const rows = compact(markets || []).map((m) => {
const infraFee = new BigNumber(m.fees.factors.infrastructureFee);
const makerFee = new BigNumber(m.fees.factors.makerFee);
@@ -88,9 +100,9 @@ export const MarketFees = ({
});
return (
<div className="border rounded-sm border-default">
<div className="border rounded-lg md:rounded-sm overflow-hidden border-default">
<AgGrid
columnDefs={useFeesTableColumnDefs()}
columnDefs={colDef}
rowData={rows}
getRowId={({ data }) => data.id}
defaultColDef={feesTableDefaultColDef}
@@ -38,7 +38,7 @@ export const FillsContainer = () => {
);
};
const useFillsStore = create<DataGridSlice>()(
export const useFillsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_fills_store',
})
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useFillsStore } from './fills-container';
export const FillsSettings = () => (
<GridSettings
updateGridStore={useFillsStore((store) => store.updateGridStore)}
/>
);
@@ -1 +1,2 @@
export * from './fills-container';
export * from './fills-settings';
@@ -45,7 +45,7 @@ export const FundingPaymentsContainer = ({
);
};
const useFundingPaymentsStore = create<DataGridSlice>()(
export const useFundingPaymentsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_funding_payments_store',
})
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useFundingPaymentsStore } from './funding-payments-container';
export const FundingPaymentsSettings = () => (
<GridSettings
updateGridStore={useFundingPaymentsStore((store) => store.updateGridStore)}
/>
);
@@ -1 +1,2 @@
export * from './funding-payments-container';
export * from './funding-payments-settings';
@@ -0,0 +1,24 @@
import { useT } from '../../lib/use-t';
import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
export const GridSettings = ({
updateGridStore,
}: {
updateGridStore: (gridStore: DataGridStore) => void;
}) => {
const t = useT();
return (
<Button
onClick={() =>
updateGridStore({
columnState: undefined,
filterModel: undefined,
})
}
size="extra-small"
>
{t('Reset Columns')}
</Button>
);
};
@@ -2,7 +2,15 @@ import type { HTMLAttributes } from 'react';
import classNames from 'classnames';
type TagProps = {
color?: 'yellow' | 'green' | 'blue' | 'purple' | 'pink' | 'orange' | 'none';
color?:
| 'yellow'
| 'green'
| 'blue'
| 'purple'
| 'pink'
| 'orange'
| 'red'
| 'none';
};
export const Tag = ({
color = 'none',
+48
View File
@@ -0,0 +1,48 @@
import type { ComponentProps } from 'react';
import type { Tag } from './tag';
import classNames from 'classnames';
// rainbow-ish order
export const TIER_COLORS: Array<ComponentProps<typeof Tag>['color']> = [
'none', // worst tier if 8 tiers, otherwise relative to the best tier
'red',
'pink',
'orange',
'yellow',
'green',
'blue',
'purple', // best tier
];
export const getTierColor = (tier: number, max = TIER_COLORS.length) => {
const available =
max < TIER_COLORS.length
? TIER_COLORS.slice(TIER_COLORS.length - max)
: TIER_COLORS;
const tiers = Object.keys(available).length;
let index = Math.abs(tier - 1);
if (tier >= tiers) {
index = index % tiers;
}
return available[index];
};
export const getTierGradient = (tier: number, max = TIER_COLORS.length) =>
classNames({
'from-vega-yellow-400 dark:from-vega-yellow-600 to-20% bg-highlight':
'yellow' === getTierColor(tier, max),
'from-vega-green-400 dark:from-vega-green-600 to-20% bg-highlight':
'green' === getTierColor(tier, max),
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
'blue' === getTierColor(tier, max),
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
'purple' === getTierColor(tier, max),
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
'pink' === getTierColor(tier, max),
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
'orange' === getTierColor(tier, max),
'from-vega-red-400 dark:from-vega-red-600 to-20% bg-highlight':
'red' === getTierColor(tier, max),
'from-vega-clight-600 dark:from-vega-cdark-600 to-20% bg-highlight':
'none' === getTierColor(tier, max),
});
@@ -27,7 +27,7 @@ export const MarketHeader = () => {
title={
<Popover
open={open}
onChange={setOpen}
onOpenChange={setOpen}
trigger={
<HeaderTitle>
<span>
@@ -1,9 +1,5 @@
import { act, renderHook } from '@testing-library/react';
import {
FilterStatusValue,
STORAGE_KEY,
useOrderListGridState,
} from './orders-container';
import { STORAGE_KEY, useOrderListGridState } from './orders-container';
import { Filter } from '@vegaprotocol/orders';
import { OrderType } from '@vegaprotocol/types';
@@ -16,31 +12,6 @@ describe('useOrderListGridState', () => {
return renderHook(() => useOrderListGridState(filter));
};
it.each(Object.values(Filter))(
'providers correct AgGrid filter for %s',
(filter) => {
const { result } = setup(filter);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
status: {
value: FilterStatusValue[filter],
},
},
});
}
);
it('provides correct AgGrid filter for all', () => {
const { result } = setup(undefined);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: undefined,
});
});
it.each(Object.values(Filter))(
'sets and stores column state and filters for %s',
(filter) => {
@@ -59,12 +30,7 @@ describe('useOrderListGridState', () => {
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
filterModel,
});
const columnState = [{ colId: 'status', width: 200 }];
@@ -77,12 +43,7 @@ describe('useOrderListGridState', () => {
expect(result.current.gridState).toEqual({
columnState,
filterModel: {
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
filterModel,
});
const storeKeyMap = {
@@ -9,6 +9,7 @@ import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { OrderStatus } from '@vegaprotocol/types';
import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t';
import { GridSettings } from '../grid-settings/grid-settings';
const resolveNoRowsMessage = (
filter: Filter | undefined,
@@ -38,6 +39,24 @@ export const FilterStatusValue = {
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
};
export const DefaultFilterModel = {
[Filter.Open]: {
status: {
value: FilterStatusValue[Filter.Open],
},
},
[Filter.Closed]: {
status: {
value: FilterStatusValue[Filter.Closed],
},
},
[Filter.Rejected]: {
status: {
value: FilterStatusValue[Filter.Rejected],
},
},
};
export interface OrderContainerProps {
filter?: Filter;
}
@@ -54,7 +73,8 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
(newState) => {
updateGridState(filter, newState);
},
AUTO_SIZE_COLUMNS
AUTO_SIZE_COLUMNS,
filter && DefaultFilterModel[filter]
);
if (!pubKey) {
@@ -80,7 +100,7 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
};
export const STORAGE_KEY = 'vega_order_list_store';
const useOrderListStore = create<{
export const useOrderListStore = create<{
open: DataGridStore;
closed: DataGridStore;
rejected: DataGridStore;
@@ -149,34 +169,19 @@ export const useOrderListGridState = (filter: Filter | undefined) => {
case Filter.Open: {
return {
columnState: store.open.columnState,
filterModel: {
...store.open.filterModel,
status: {
value: FilterStatusValue[Filter.Open],
},
},
filterModel: store.open.filterModel,
};
}
case Filter.Closed: {
return {
columnState: store.closed.columnState,
filterModel: {
...store.closed.filterModel,
status: {
value: FilterStatusValue[Filter.Closed],
},
},
filterModel: store.closed.filterModel,
};
}
case Filter.Rejected: {
return {
columnState: store.rejected.columnState,
filterModel: {
...store.rejected.filterModel,
status: {
value: FilterStatusValue[Filter.Rejected],
},
},
filterModel: store.rejected.filterModel,
};
}
default: {
@@ -187,3 +192,14 @@ export const useOrderListGridState = (filter: Filter | undefined) => {
return { gridState, updateGridState };
};
export const OrdersSettings = ({ filter }: { filter?: Filter }) => {
const updateGridState = useOrderListStore((state) => state.update);
return (
<GridSettings
updateGridStore={(gridStore: DataGridStore) =>
updateGridState(filter, gridStore)
}
/>
);
};
@@ -1 +1,2 @@
export * from './positions-container';
export * from './positions-settings';
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { usePositionsStore } from './positions-container';
export const PositionsSettings = () => (
<GridSettings
updateGridStore={usePositionsStore((store) => store.updateGridStore)}
/>
);
@@ -231,6 +231,7 @@ export const RewardsContainer = () => {
title={t('Rewards history')}
className="lg:col-span-full"
loading={rewardsLoading}
noBackgroundOnMobile={true}
>
<RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)}
@@ -99,16 +99,25 @@ describe('RewardsHistoryTable', () => {
it('renders table with accounts summed up by asset', () => {
render(<RewardHistoryTable {...props} />);
const container = within(
const containerLeft = within(
document.querySelector('.ag-pinned-left-cols-container') as HTMLElement
);
const pinnedRows = containerLeft.getAllByRole('row');
const containerCenter = within(
document.querySelector('.ag-center-cols-container') as HTMLElement
);
const rows = container.getAllByRole('row');
const rows = containerCenter.getAllByRole('row');
expect(rows).toHaveLength(
Object.keys(groupBy(rewardSummaries, 'node.assetId')).length
);
let row = within(rows[0]);
let cells = row.getAllByRole('gridcell');
let pinnedRow = within(pinnedRows[0]);
let cells = [
...pinnedRow.getAllByRole('gridcell'),
...row.getAllByRole('gridcell'),
];
let assetCell = getCell(cells, 'asset.symbol');
expect(assetCell.getByTestId('stack-cell-primary')).toHaveTextContent(
@@ -140,7 +149,11 @@ describe('RewardsHistoryTable', () => {
// Second row
row = within(rows[1]);
cells = row.getAllByRole('gridcell');
pinnedRow = within(pinnedRows[1]);
cells = [
...pinnedRow.getAllByRole('gridcell'),
...row.getAllByRole('gridcell'),
];
assetCell = getCell(cells, 'asset.symbol');
expect(assetCell.getByTestId('stack-cell-primary')).toHaveTextContent(
@@ -93,6 +93,7 @@ export const RewardsHistoryContainer = ({
const defaultColDef = {
flex: 1,
minWidth: 62,
resizable: true,
sortable: true,
};
@@ -195,6 +196,8 @@ export const RewardHistoryTable = ({
return <StackedCell primary={value} secondary={data.asset.name} />;
},
sort: 'desc',
pinned: 'left',
width: 150,
},
{
field: 'infrastructureFees',
@@ -257,7 +260,7 @@ export const RewardHistoryTable = ({
return (
<div>
<div className="mb-2 flex items-center justify-between gap-2">
<div className="mb-2 flex flex-wrap items-center justify-between gap-2">
<h4 className="text-muted flex items-center gap-2 text-sm">
<label htmlFor="fromEpoch">{t('From epoch')}</label>
<EpochInput
@@ -329,15 +332,17 @@ export const RewardHistoryTable = ({
</TradingButton>
</div>
</div>
<AgGrid
columnDefs={columnDefs}
defaultColDef={defaultColDef}
rowData={rowData}
rowHeight={45}
domLayout="autoHeight"
// Show loading message without wiping out the current rows
overlayNoRowsTemplate={loading ? t('Loading...') : t('No rows')}
/>
<div className="border rounded-lg md:rounded-sm overflow-hidden border-default">
<AgGrid
columnDefs={columnDefs}
defaultColDef={defaultColDef}
rowData={rowData}
rowHeight={45}
domLayout="autoHeight"
// Show loading message without wiping out the current rows
overlayNoRowsTemplate={loading ? t('Loading...') : t('No rows')}
/>
</div>
</div>
);
};
@@ -1 +1,3 @@
export * from './stop-orders-container';
export * from './stop-orders-settings';
@@ -35,7 +35,7 @@ export const StopOrdersContainer = () => {
);
};
const useStopOrdersStore = create<DataGridSlice>()(
export const useStopOrdersStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_stop_orders_store',
})
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useStopOrdersStore } from './stop-orders-container';
export const StopOrdersSettings = () => (
<GridSettings
updateGridStore={useStopOrdersStore((store) => store.updateGridStore)}
/>
);
+1
View File
@@ -0,0 +1 @@
export * from './table';
@@ -1,13 +1,17 @@
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import { forwardRef, type ReactNode, type HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
type TableColumnDefinition = {
displayName?: ReactNode;
name: string;
tooltip?: string;
className?: string;
testId?: string;
};
type TableProps = {
@@ -42,7 +46,7 @@ export const Table = forwardRef<
key={name}
col-id={name}
className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100 font-normal',
'px-5 py-3 text-xs text-vega-clight-100 dark:text-vega-cdark-100 font-normal',
INNER_BORDER_STYLE
)}
>
@@ -66,7 +70,7 @@ export const Table = forwardRef<
ref={ref}
className={classNames(
'w-full',
'border-separate border rounded-md border-spacing-0',
'border-separate border rounded-md border-spacing-0 overflow-hidden',
BORDER_COLOR,
GRADIENT,
className
@@ -82,10 +86,10 @@ export const Table = forwardRef<
'max-md:flex flex-col w-full': !noCollapse,
})}
>
{columns.map(({ name, displayName, className }, j) => (
{columns.map(({ name, displayName, className, testId }, j) => (
<td
className={classNames(
'px-5 py-3 text-base',
'px-5 py-3',
{
'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse,
@@ -110,7 +114,7 @@ export const Table = forwardRef<
{displayName}
</span>
)}
<span>{d[name]}</span>
<span data-testid={`${testId || name}-${i}`}>{d[name]}</span>
</td>
))}
</tr>
@@ -1 +1,2 @@
export * from './trades-container';
export * from './trades-settings';
@@ -17,7 +17,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
return <TradesManager marketId={marketId} gridProps={gridStoreCallbacks} />;
};
const useTradesStore = create<DataGridSlice>()(
export const useTradesStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_trades_store',
})
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useTradesStore } from './trades-container';
export const TradesSettings = () => (
<GridSettings
updateGridStore={useTradesStore((store) => store.updateGridStore)}
/>
);
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.73.9
VEGA_VERSION=v0.73.10
LOCAL_SERVER=false
+2 -1
View File
@@ -1,2 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.73.9
VEGA_VERSION=v0.73.10
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
VEGA_VERSION=v0.73.8
VEGA_VERSION=v0.73.10
LOCAL_SERVER=false
+13 -9
View File
@@ -27,8 +27,6 @@ MIN_VOLUME_VALUE_0 = "min-volume-value-0"
MIN_VOLUME_VALUE_1 = "min-volume-value-1"
MY_VOLUME_VALUE_0 = "my-volume-value-0"
MY_VOLUME_VALUE_1 = "my-volume-value-1"
YOUR_TIER_0 = "your-tier-0"
YOUR_TIER_1 = "your-tier-1"
ORDER_SIZE = "order-size"
ORDER_PRICE = "order-price"
DISCOUNT_PILL = "discount-pill"
@@ -52,6 +50,7 @@ REQUIRED_EPOCHS_VALUE_1 = "required-epochs-value-1"
FILLS = "Fills"
TAB_FILLS = "tab-fills"
FEE_BREAKDOWN_TOOLTIP = "fee-breakdown-tooltip"
PINNED_ROW_LOCATOR = ".ag-pinned-left-cols-container .ag-row"
ROW_LOCATOR = ".ag-center-cols-container .ag-row"
# Col-Ids:
COL_INSTRUMENT_CODE = '[col-id="market.tradableInstrument.instrument.code"]'
@@ -405,10 +404,10 @@ def test_fees_page_referral_discount_program_referral_benefits(
@pytest.mark.parametrize(
"tier, discount_program, my_volume_test_id, my_volume_value, your_tier",
[
(1, "volume", "my-volume-value-0", "103", "your-tier-0"),
(2, "volume", "my-volume-value-1", "206", "your-tier-1"),
(1, "referral", "my-volume-value-0", "103", "your-tier-0"),
(2, "referral", "my-volume-value-1", "206", "your-tier-1"),
(1, "volume", "my-volume-value-0", "103", "your-volume-tier-0"),
(2, "volume", "my-volume-value-1", "206", "your-volume-tier-1"),
(1, "referral", "my-volume-value-0", "103", "your-referral-tier-0"),
(2, "referral", "my-volume-value-1", "206", "your-referral-tier-1"),
],
)
@pytest.mark.usefixtures("risk_accepted", "auth", "market_ids")
@@ -439,8 +438,8 @@ def test_fees_page_discount_program_discount(
expect(page.get_by_test_id(REQUIRED_EPOCHS_VALUE_0)).to_have_text("1")
expect(page.get_by_test_id(REQUIRED_EPOCHS_VALUE_1)).to_have_text("2")
expect(page.get_by_test_id(your_tier)).to_be_visible()
expect(page.get_by_test_id(your_tier)).to_have_text("Your tier")
expect(page.get_by_test_id(your_tier).nth(1)).to_be_visible()
expect(page.get_by_test_id(your_tier).nth(1)).to_have_text("Your tier")
@pytest.mark.parametrize(
@@ -461,8 +460,9 @@ def test_fees_page_discount_program_fees_by_market(
vega_instance, tier, discount_program, market_ids
)
page.goto("/#/fees")
pinned = page.locator(PINNED_ROW_LOCATOR)
row = page.locator(ROW_LOCATOR)
expect(row.locator(COL_CODE)).to_have_text("BTC:DAI_2023Futr")
expect(pinned.locator(COL_CODE)).to_have_text("BTC:DAI_2023Futr")
expect(row.locator(COL_FEE_AFTER_DISCOUNT)).to_have_text(fees_after_discount)
expect(row.locator(COL_INFRA_FEE)).to_have_text("0.05%")
expect(row.locator(COL_MAKER_FEE)).to_have_text("10%")
@@ -639,6 +639,8 @@ def test_fills_maker_fee_tooltip_discount_program(
change_keys(page, vega_instance, MM_WALLET.name)
page.get_by_test_id(FILLS).click()
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
row.locator(COL_FEE).hover()
expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text(
f"If the market was activeFee revenue to be received by the maker, takers' fee discounts already applied.During continuous trading the maker pays no infrastructure and liquidity fees.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee-{fee} tDAITotal fees-{fee} tDAI"
@@ -676,6 +678,8 @@ def test_fills_taker_fee_tooltip_discount_program(
page.goto(f"/#/markets/{market_id}")
page.get_by_test_id(FILLS).click()
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
row.locator(COL_FEE).hover()
expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text(
f"If the market was activeFees to be paid by the taker; discounts are already applied.Infrastructure fee{infra_fee} tDAILiquidity fee0.00 tDAIMaker fee{maker_fee} tDAITotal fees{total_fee} tDAI"
@@ -65,16 +65,17 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
page.wait_for_selector(".ag-center-cols-container .ag-row")
expect(
page.locator(".ag-center-cols-container .ag-row [col-id='remaining']")
page.locator(".ag-center-cols-container .ag-row [col-id='remaining']").first
).to_have_text("99")
expect(
page.locator(".ag-center-cols-container .ag-row [col-id='size']")
page.locator(".ag-center-cols-container .ag-row [col-id='size']").first
).to_have_text("-102")
page.pause()
expect(
page.locator(".ag-center-cols-container .ag-row [col-id='type'] ")
page.locator(".ag-center-cols-container .ag-row [col-id='type'] ").first
).to_have_text("Limit (Iceberg)")
expect(
page.locator(".ag-center-cols-container .ag-row [col-id='status']")
page.locator(".ag-center-cols-container .ag-row [col-id='status']").first
).to_have_text("Active")
expect(page.get_by_test_id("price-10100000")).to_be_visible
expect(page.get_by_test_id("ask-vol-10100000")).to_have_text("3")
+6 -2
View File
@@ -206,13 +206,17 @@ def test_auction_uncross_fees(continuous_market, vega: VegaServiceNull, page: Pa
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Fills").click()
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover()
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_role("gridcell", name="0.00 tDAI").nth(0).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
"If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
)
change_keys(page, vega, "market_maker")
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover()
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_role("gridcell", name="0.00 tDAI").nth(0).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
"If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
)
@@ -9,16 +9,16 @@ def test_market_selector(continuous_market, page: Page):
page.get_by_test_id("header-title").click()
# 6001-MARK-066
expect(page.get_by_test_id("market-selector")).to_be_visible()
# 6001-MARK-021
# 6001-MARK-022
# 6001-MARK-024
# 6001-MARK-025
btc_market = page.locator('[data-testid="market-selector-list"] a')
expect(btc_market.locator("h3")).to_have_text("BTC:DAI_2023Futr")
expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text(
"0.00"
)
# tbd - 5465
# expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text(
# "1"
# )
expect(btc_market.locator('[data-testid="market-selector-price"]')).to_have_text(
"107.50 tDAI"
)
@@ -43,7 +43,6 @@ def test_market_selector_filter(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("header-title").click()
# 6001-MARK-027
page.get_by_test_id("product-Spot").click()
expect(page.get_by_test_id("market-selector-list")).to_contain_text(
"Spot markets coming soon."
@@ -58,8 +57,9 @@ def test_market_selector_filter(continuous_market, page: Page):
# 6001-MARK-029
page.get_by_test_id("search-term").fill("btc")
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text(
"BTC:DAI_2023107.50 tDAI0.00"
# tbd - 5465
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
"BTC:DAI_2023107.50 tDAI"
)
page.get_by_test_id("search-term").clear()
@@ -83,6 +83,7 @@ def test_market_selector_filter(continuous_market, page: Page):
page.get_by_test_id("asset-trigger").click()
page.get_by_role("menuitemcheckbox").nth(0).get_by_text("tDAI").click()
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text(
"BTC:DAI_2023107.50 tDAI0.00"
# tbd - 5465
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
"BTC:DAI_2023107.50 tDAI"
)
@@ -16,11 +16,11 @@ def verify_data_grid(page: Page, data_test_id, expected_pattern):
expect(
page.locator(
f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
)
).first
).to_be_visible()
actual_text = page.locator(
f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
).text_content()
).first.text_content()
lines = actual_text.strip().split("\n")
for expected, actual in zip(expected_pattern, lines):
# We are using regex so that we can run tests in different timezones.
@@ -0,0 +1,36 @@
import pytest
from playwright.sync_api import expect, Page
settings_icon = "icon-cog"
settings_column_btn = "popover-trigger"
settings_close_btn = "settings-close"
split_view_view = "split-view-view"
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_column_settings_is_visible(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
expect(page.get_by_test_id(split_view_view).get_by_test_id(settings_column_btn)).to_be_visible()
page.goto("/#/portfolio")
expect(page.get_by_test_id(split_view_view).get_by_test_id(settings_column_btn).nth(0)).to_be_visible()
expect(page.get_by_test_id(split_view_view).get_by_test_id(settings_column_btn).nth(1)).to_be_visible()
page.goto(f"/#/markets/all")
expect(page.get_by_test_id(settings_column_btn)).to_be_visible()
page.click('[data-testid="Proposed markets"]')
expect(page.get_by_test_id(settings_column_btn)).to_be_visible()
page.click('[data-testid="Closed markets"]')
expect(page.get_by_test_id(settings_column_btn)).to_be_visible()
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_can_reset_columns_state(continuous_market, page: Page):
page.goto(f"/#/markets/all")
col_market = page.locator('[col-id="tradableInstrument.instrument.code"]').first
col_settlement_asset = page.locator('[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"]').first
col_market.drag_to(col_settlement_asset)
# Check the attribute of the dragged element
attribute_value = col_market.get_attribute("aria-colindex")
assert attribute_value != "1"
page.get_by_test_id(settings_column_btn).click()
page.get_by_role("button", name="Reset Columns").click()
attribute_value_after_reset = col_market.get_attribute("aria-colindex")
assert attribute_value_after_reset == "1"
+8
View File
@@ -14,6 +14,7 @@ import { Withdraw } from '../client-pages/withdraw';
import { Transfer } from '../client-pages/transfer';
import { Fees } from '../client-pages/fees';
import { Rewards } from '../client-pages/rewards';
import { Teams } from '../client-pages/teams';
import { Routes as AppRoutes } from '../lib/links';
import { LayoutWithSky } from '../client-pages/referrals/layout';
import { Referrals } from '../client-pages/referrals/referrals';
@@ -92,6 +93,12 @@ export const useRouterConfig = (): RouteObject[] => {
],
}
: undefined,
featureFlags.TEAM_COMPETITION
? {
path: AppRoutes.TEAMS,
element: <Teams />,
}
: undefined,
{
path: 'fees/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
@@ -176,6 +183,7 @@ export const useRouterConfig = (): RouteObject[] => {
},
],
},
{
path: '*',
element: <NotFound />,
+6
View File
@@ -3,6 +3,9 @@ fragment MarginFields on MarginLevels {
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
asset {
id
}
@@ -33,6 +36,9 @@ subscription MarginsSubscription($partyId: ID!) {
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
timestamp
}
}
+9 -3
View File
@@ -3,21 +3,21 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } };
export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginFactor: string, marginMode: Types.MarginMode, orderMarginLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } };
export type MarginsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type MarginsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
export type MarginsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginFactor: string, marginMode: Types.MarginMode, orderMarginLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
export type MarginsSubscriptionSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } };
export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginFactor: string, marginMode: Types.MarginMode, orderMarginLevel: string, timestamp: any } };
export const MarginFieldsFragmentDoc = gql`
fragment MarginFields on MarginLevels {
@@ -25,6 +25,9 @@ export const MarginFieldsFragmentDoc = gql`
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
asset {
id
}
@@ -85,6 +88,9 @@ export const MarginsSubscriptionDocument = gql`
searchLevel
initialLevel
collateralReleaseLevel
marginFactor
marginMode
orderMarginLevel
timestamp
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ const AccountBreakdown = ({
variables: { partyId, assetId },
update: ({ data }) => {
if (gridRef.current?.api && data?.breakdown) {
gridRef.current?.api.setRowData(data?.breakdown);
gridRef.current?.api.setGridOption('rowData', data?.breakdown);
return true;
}
return false;
@@ -40,6 +40,9 @@ const update = (
searchLevel: delta.searchLevel,
initialLevel: delta.initialLevel,
collateralReleaseLevel: delta.collateralReleaseLevel,
marginFactor: delta.marginFactor,
marginMode: delta.marginMode,
orderMarginLevel: delta.orderMarginLevel,
asset: {
__typename: 'Asset',
id: delta.asset,
+11 -3
View File
@@ -8,9 +8,17 @@ export const StackedCell = ({
secondary: ReactNode;
}) => {
return (
<div className="leading-4 text-ellipsis whitespace-nowrap overflow-hidden">
<div data-testid="stack-cell-primary">{primary}</div>
<div data-testid="stack-cell-secondary" className="text-muted">
<div className="leading-4">
<div
className="text-ellipsis whitespace-nowrap overflow-hidden"
data-testid="stack-cell-primary"
>
{primary}
</div>
<div
data-testid="stack-cell-secondary"
className="text-ellipsis whitespace-nowrap overflow-hidden text-muted"
>
{secondary}
</div>
</div>
@@ -65,18 +65,9 @@ export const DateRangeFilter = forwardRef(
useImperativeHandle(ref, () => {
return {
doesFilterPass(params: IDoesFilterPassParams) {
const { api, colDef, column, columnApi, context } = props;
const { column } = props;
const { node } = params;
const rowValue = props.valueGetter({
api,
colDef,
column,
columnApi,
context,
data: node.data,
getValue: (field) => node.data[field],
node,
});
const rowValue = props.getValue(node, column);
if (
value.start &&
rowValue &&
+2 -11
View File
@@ -19,18 +19,9 @@ export const SetFilter = forwardRef(
useImperativeHandle(ref, () => {
return {
doesFilterPass(params: IDoesFilterPassParams) {
const { api, colDef, column, columnApi, context } = props;
const { column } = props;
const { node } = params;
const getValue = props.valueGetter({
api,
colDef,
column,
columnApi,
context,
data: node.data,
getValue: (field) => node.data[field],
node,
});
const getValue = props.getValue(node, column);
return Array.isArray(value)
? value.includes(getValue)
: getValue === value;
@@ -61,7 +61,7 @@ describe('useDataGridEvents', () => {
// column state was not updated, so the default width provided by the
// col def should be set
expect(gridRef?.current?.columnApi.getColumnState()[0].width).toEqual(
expect(gridRef?.current?.api.getColumnState()[0].width).toEqual(
gridProps.columnDefs[0].width
);
// no filters set
@@ -107,16 +107,57 @@ describe('useDataGridEvents', () => {
columnState: [colState],
};
setup(initialState, jest.fn());
setup(initialState, jest.fn(), undefined);
await waitFor(() => {
expect(gridRef?.current?.api.getFilterModel()['id']).toEqual(idFilter);
expect(gridRef?.current?.columnApi.getColumnState()[0]).toEqual(
expect(gridRef?.current?.api.getColumnState()[0]).toEqual(
expect.objectContaining(colState)
);
});
});
it('applies default filter model', async () => {
const idFilter = {
filter: 1,
filterType: 'number',
type: 'equals',
};
setup({}, jest.fn(), undefined, {
id: idFilter,
});
await waitFor(() => {
expect(gridRef?.current?.api.getFilterModel()['id']).toEqual(idFilter);
});
});
it('default filter overwrites stored filter model', async () => {
const idFilter = {
filter: 1,
filterType: 'number',
type: 'equals',
};
setup(
{
filterModel: {
id: { ...idFilter, filter: 2 },
},
},
jest.fn(),
undefined,
{
id: idFilter,
}
);
await waitFor(() => {
expect(gridRef?.current?.api.getFilterModel()['id']).toEqual(idFilter);
});
});
it('ignores events that were not made via the UI', async () => {
const callback = jest.fn();
const initialState = {
@@ -130,7 +171,7 @@ describe('useDataGridEvents', () => {
// Set col width multiple times
await act(async () => {
gridRef?.current?.columnApi.setColumnWidth('id', newWidth);
gridRef?.current?.api.setColumnWidth('id', newWidth);
});
expect(callback).not.toHaveBeenCalled();
@@ -150,14 +191,14 @@ describe('useDataGridEvents', () => {
};
const { rerender } = setup(initialState, callback, ['id']);
jest.spyOn(gridRef?.current?.columnApi, 'autoSizeColumns');
if (gridRef?.current?.api) {
jest.spyOn(gridRef?.current?.api, 'autoSizeColumns');
}
rerender(<TestComponent hookParams={[initialState, callback, ['id']]} />);
act(() => {
gridRef?.current?.api.setRowData([{ id: 'test-id' }]);
gridRef?.current?.api.setGridOption('rowData', [{ id: 'test-id' }]);
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(gridRef?.current?.columnApi.autoSizeColumns).toHaveBeenCalledWith([
'id',
]);
expect(gridRef?.current?.api.autoSizeColumns).toHaveBeenCalledWith(['id']);
});
});
+41 -20
View File
@@ -7,8 +7,9 @@ import {
type FirstDataRenderedEvent,
type SortChangedEvent,
type GridReadyEvent,
GridApi,
} from 'ag-grid-community';
import { useCallback } from 'react';
import { useCallback, useEffect, useRef } from 'react';
type State = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -19,8 +20,32 @@ type State = {
export const useDataGridEvents = (
state: State,
callback: (data: State) => void,
autoSizeColumns?: string[]
autoSizeColumns?: string[],
defaultFilterModel?: State['filterModel']
) => {
const apiRef = useRef<GridApi | undefined>();
const hasStateRef = useRef(Boolean(state.columnState || state.filterModel));
useEffect(() => {
if (apiRef.current?.isDestroyed()) {
apiRef.current = undefined;
}
const hasState = Boolean(state.columnState || state.filterModel);
if (apiRef.current && hasStateRef.current && !hasState) {
if (!state.columnState) {
apiRef.current.resetColumnState();
apiRef.current.sizeColumnsToFit();
if (autoSizeColumns?.length) {
apiRef.current.autoSizeColumns(autoSizeColumns);
}
}
if (!state.filterModel) {
apiRef.current.setFilterModel(defaultFilterModel);
}
}
hasStateRef.current = hasState;
}, [state, defaultFilterModel, autoSizeColumns]);
/**
* Callback for filter events
*/
@@ -39,11 +64,7 @@ export const useDataGridEvents = (
* store callback unnecessarily
*/
const onDebouncedColumnChange = useCallback(
({
columnApi,
source,
finished,
}: ColumnResizedEvent | ColumnMovedEvent) => {
({ api, source, finished }: ColumnResizedEvent | ColumnMovedEvent) => {
if (!finished) return;
// only call back on user interactions, and not events triggered from the api
@@ -57,7 +78,7 @@ export const useDataGridEvents = (
return;
}
const columnState = columnApi.getColumnState();
const columnState = api.getColumnState();
callback({ columnState });
},
@@ -68,8 +89,8 @@ export const useDataGridEvents = (
* Callback for sort and visible events
*/
const onColumnChange = useCallback(
({ columnApi }: SortChangedEvent | ColumnVisibleEvent) => {
const columnState = columnApi.getColumnState();
({ api }: SortChangedEvent | ColumnVisibleEvent) => {
const columnState = api.getColumnState();
callback({ columnState });
},
[callback]
@@ -80,11 +101,11 @@ export const useDataGridEvents = (
* State only applied if found, otherwise columns sized to fit available space
*/
const onGridReady = useCallback(
({ api, columnApi }: GridReadyEvent) => {
if (!api || !columnApi) return;
({ api }: GridReadyEvent) => {
apiRef.current = api;
if (!api) return;
if (state.columnState) {
columnApi.applyColumnState({
api.applyColumnState({
state: state.columnState,
applyOrder: true,
});
@@ -92,18 +113,18 @@ export const useDataGridEvents = (
api.sizeColumnsToFit();
}
if (state.filterModel) {
api.setFilterModel(state.filterModel);
if (state.filterModel || defaultFilterModel) {
api.setFilterModel({ ...state.filterModel, ...defaultFilterModel });
}
},
[state]
[state, defaultFilterModel]
);
const onFirstDataRendered = useCallback(
({ columnApi }: FirstDataRenderedEvent) => {
if (!columnApi) return;
({ api }: FirstDataRenderedEvent) => {
if (!api) return;
if (!state?.columnState && autoSizeColumns?.length) {
columnApi.autoSizeColumns(autoSizeColumns);
api.autoSizeColumns(autoSizeColumns);
}
},
[state, autoSizeColumns]
@@ -13,6 +13,7 @@ import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
import { DealTicket } from './deal-ticket';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useT } from '../../use-t';
import { MarginModeSelector } from './margin-mode-selector';
interface DealTicketContainerProps {
marketId: string;
@@ -51,21 +52,26 @@ export const DealTicketContainer = ({
reload={reload}
>
{market && marketData ? (
featureFlags.STOP_ORDERS && showStopOrder ? (
<StopOrder
market={market}
marketPrice={marketPrice}
submit={(stopOrdersSubmission) => create({ stopOrdersSubmission })}
/>
) : (
<DealTicket
{...props}
market={market}
marketPrice={marketPrice}
marketData={marketData}
submit={(orderSubmission) => create({ orderSubmission })}
/>
)
<>
<MarginModeSelector marketId={marketId} />
{featureFlags.STOP_ORDERS && showStopOrder ? (
<StopOrder
market={market}
marketPrice={marketPrice}
submit={(stopOrdersSubmission) =>
create({ stopOrdersSubmission })
}
/>
) : (
<DealTicket
{...props}
market={market}
marketPrice={marketPrice}
marketData={marketData}
submit={(orderSubmission) => create({ orderSubmission })}
/>
)}
</>
) : (
<p>{t('Could not load market')}</p>
)}
@@ -649,7 +649,6 @@ const formatTrigger = (
Number(triggerTrailingPercentOffset) || 0
).toFixed(1),
})
}
}`;
const SubmitButton = ({
@@ -0,0 +1,66 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { marginModeDataProvider } from '@vegaprotocol/positions';
import { MarginMode, useVegaWallet } from '@vegaprotocol/wallet';
import * as Types from '@vegaprotocol/types';
import { useVegaTransactionStore } from '@vegaprotocol/web3';
export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data: marginMode } = useDataProvider({
dataProvider: marginModeDataProvider,
variables: {
partyId: pubKey || '',
marketId,
},
skip: !pubKey,
});
const create = useVegaTransactionStore((state) => state.create);
const disabled = isReadOnly;
return (
<div className="grid grid-cols-2 gap-2 mb-2">
<TradingButton
disabled={disabled}
size="extra-small"
onClick={() =>
create({
updateMarginMode: {
market_id: marketId,
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
},
})
}
intent={
!marginMode ||
marginMode.marginMode === Types.MarginMode.MARGIN_MODE_CROSS_MARGIN
? Intent.Primary
: Intent.None
}
>
Cross
</TradingButton>
<TradingButton
disabled={disabled}
size="extra-small"
onClick={() =>
create({
updateMarginMode: {
market_id: marketId,
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
marginFactor: '0.1',
},
})
}
intent={
marginMode?.marginMode ===
Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN
? Intent.Primary
: Intent.None
}
>
Isolated {marginMode?.margin_factor || '10'}x
</TradingButton>
</div>
);
};
+4 -1
View File
@@ -17,9 +17,12 @@ export const useGetFaucetError = (error: TxError | null, symbol?: string) => {
'The faucet transaction was rejected by the connected Ethereum wallet'
),
};
if (!error) {
return error;
}
// render a customized failure message from the map above or fallback
// to a non generic error message
return error && 'reason' in error && reasonMap[error.reason]
return 'reason' in error && reasonMap[error.reason]
? reasonMap[error.reason]
: t('Faucet of {{symbol}} failed', { symbol: symbol || '' });
};
@@ -368,6 +368,12 @@ export const compileFeatureFlags = (refresh = false): FeatureFlags => {
process.env['NX_VOLUME_DISCOUNTS']
) as string
),
TEAM_COMPETITION: TRUTHY.includes(
windowOrDefault(
'NX_TEAM_COMPETITION',
process.env['NX_TEAM_COMPETITION']
) as string
),
};
const EXPLORER_FLAGS = {
+1
View File
@@ -28,6 +28,7 @@ export type CosmicElevatorFlags = Pick<
| 'GOVERNANCE_TRANSFERS'
| 'VOLUME_DISCOUNTS'
| 'DISABLE_CLOSE_POSITION'
| 'TEAM_COMPETITION'
>;
export type Configuration = z.infer<typeof tomlConfigSchema>;
export const CUSTOM_NODE_KEY = 'custom' as const;
@@ -84,6 +84,7 @@ const COSMIC_ELEVATOR_FLAGS = {
GOVERNANCE_TRANSFERS: z.optional(z.boolean()),
VOLUME_DISCOUNTS: z.optional(z.boolean()),
DISABLE_CLOSE_POSITION: z.optional(z.boolean()),
TEAM_COMPETITION: z.optional(z.boolean()),
};
const EXPLORER_FLAGS = {
@@ -36,7 +36,7 @@ export const FundingPaymentsManager = ({
dataProvider: fundingPaymentsWithMarketProvider,
update: ({ data }) => {
if (data?.length && gridRef.current?.api) {
gridRef.current?.api.setRowData(data);
gridRef.current?.api.setGridOption('rowData', data);
return true;
}
return false;
+1 -1
View File
@@ -108,7 +108,7 @@
"The fraction of the insurance pool balance that is carried over from the parent market to the successor.": "The fraction of the insurance pool balance that is carried over from the parent market to the successor.",
"The ID of the market this market succeeds.": "The ID of the market this market succeeds.",
"The length of time over which open interest is measured.": "The length of time over which open interest is measured.",
"The liquidity price range is a {{{liquidityPriceRange}} difference from the mid price.": "The liquidity price range is a {{{liquidityPriceRange}} difference from the mid price.",
"The liquidity price range is a {{liquidityPriceRange}} difference from the mid price.": "The liquidity price range is a {{liquidityPriceRange}} difference from the mid price.",
"The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.": "The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.",
"The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.": "The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.",
"The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.": "The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.",
+1
View File
@@ -236,6 +236,7 @@
"Rejected": "Rejected",
"Required epochs": "Required epochs",
"Required for next tier": "Required for next tier",
"Reset Columns": "Reset Columns",
"Resources": "Resources",
"Rewards": "Rewards",
"Rewards history": "Rewards history",
@@ -970,7 +970,7 @@ export const LiquidityPriceRangeInfoPanel = ({
/>
<p className="mb-2 mt-2 border-l-2 pl-2 text-xs">
{t(
'The liquidity price range is a {{{liquidityPriceRange}} difference from the mid price.',
'The liquidity price range is a {{liquidityPriceRange}} difference from the mid price.',
{ liquidityPriceRange }
)}
</p>
+1
View File
@@ -1,4 +1,5 @@
export * from './lib/__generated__/Positions';
export * from './lib/margin-modes-provider';
export * from './lib/positions-data-providers';
export * from './lib/positions-table';
export * from './lib/positions-manager';
+20
View File
@@ -83,3 +83,23 @@ query EstimatePosition(
}
}
}
fragment MarginMode on PartyMarginMode {
marketId
partyId
marginMode
margin_factor
min_theoretical_margin_factor
max_theoretical_leverage
atEpoch
}
query MarginModes($partyId: ID!) {
partyMarginModes(partyId: $partyId) {
edges {
node {
...MarginMode
}
}
}
}
+60 -1
View File
@@ -29,6 +29,15 @@ export type EstimatePositionQueryVariables = Types.Exact<{
export type EstimatePositionQuery = { __typename?: 'Query', estimatePosition?: { __typename?: 'PositionEstimate', margin: { __typename?: 'MarginEstimate', worstCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string }, bestCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string } }, liquidation?: { __typename?: 'LiquidationEstimate', worstCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string }, bestCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string } } | null } | null };
export type MarginModeFragment = { __typename?: 'PartyMarginMode', marketId: string, partyId: string, marginMode: Types.MarginMode, margin_factor?: string | null, min_theoretical_margin_factor?: string | null, max_theoretical_leverage?: string | null, atEpoch: number };
export type MarginModesQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type MarginModesQuery = { __typename?: 'Query', partyMarginModes?: { __typename?: 'PartyMarginModesConnection', edges?: Array<{ __typename?: 'PartyMarginModeEdge', node: { __typename?: 'PartyMarginMode', marketId: string, partyId: string, marginMode: Types.MarginMode, margin_factor?: string | null, min_theoretical_margin_factor?: string | null, max_theoretical_leverage?: string | null, atEpoch: number } } | null> | null } | null };
export const PositionFieldsFragmentDoc = gql`
fragment PositionFields on Position {
realisedPNL
@@ -46,6 +55,17 @@ export const PositionFieldsFragmentDoc = gql`
}
}
`;
export const MarginModeFragmentDoc = gql`
fragment MarginMode on PartyMarginMode {
marketId
partyId
marginMode
margin_factor
min_theoretical_margin_factor
max_theoretical_leverage
atEpoch
}
`;
export const PositionsDocument = gql`
query Positions($partyIds: [ID!]!) {
positions(filter: {partyIds: $partyIds}) {
@@ -191,4 +211,43 @@ export function useEstimatePositionLazyQuery(baseOptions?: Apollo.LazyQueryHookO
}
export type EstimatePositionQueryHookResult = ReturnType<typeof useEstimatePositionQuery>;
export type EstimatePositionLazyQueryHookResult = ReturnType<typeof useEstimatePositionLazyQuery>;
export type EstimatePositionQueryResult = Apollo.QueryResult<EstimatePositionQuery, EstimatePositionQueryVariables>;
export type EstimatePositionQueryResult = Apollo.QueryResult<EstimatePositionQuery, EstimatePositionQueryVariables>;
export const MarginModesDocument = gql`
query MarginModes($partyId: ID!) {
partyMarginModes(partyId: $partyId) {
edges {
node {
...MarginMode
}
}
}
}
${MarginModeFragmentDoc}`;
/**
* __useMarginModesQuery__
*
* To run a query within a React component, call `useMarginModesQuery` and pass it any options that fit your needs.
* When your component renders, `useMarginModesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMarginModesQuery({
* variables: {
* partyId: // value for 'partyId'
* },
* });
*/
export function useMarginModesQuery(baseOptions: Apollo.QueryHookOptions<MarginModesQuery, MarginModesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MarginModesQuery, MarginModesQueryVariables>(MarginModesDocument, options);
}
export function useMarginModesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarginModesQuery, MarginModesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MarginModesQuery, MarginModesQueryVariables>(MarginModesDocument, options);
}
export type MarginModesQueryHookResult = ReturnType<typeof useMarginModesQuery>;
export type MarginModesLazyQueryHookResult = ReturnType<typeof useMarginModesLazyQuery>;
export type MarginModesQueryResult = Apollo.QueryResult<MarginModesQuery, MarginModesQueryVariables>;
@@ -0,0 +1,38 @@
import { removePaginationWrapper } from '@vegaprotocol/utils';
import {
makeDataProvider,
makeDerivedDataProvider,
} from '@vegaprotocol/data-provider';
import {
MarginModesDocument,
type MarginModesQueryVariables,
MarginModesQuery,
MarginModeFragment,
} from './__generated__/Positions';
export const marginModesDataProvider = makeDataProvider<
MarginModesQuery,
MarginModeFragment[],
never,
never,
MarginModesQueryVariables
>({
query: MarginModesDocument,
getData: (responseData: MarginModesQuery | null) =>
removePaginationWrapper(responseData?.partyMarginModes?.edges) || [],
});
export const marginModeDataProvider = makeDerivedDataProvider<
MarginModeFragment | undefined,
never,
MarginModesQueryVariables & { marketId: string }
>(
[
(callback, client, variables) =>
marginModesDataProvider(callback, client, { partyId: variables.partyId }),
],
(data, variables) =>
(data as MarginModeFragment[]).find(
(marginMode) => marginMode.marketId === variables.marketId
)
);
+3 -2
View File
@@ -86,8 +86,7 @@ export const TradingView = ({
// Show volume study by default, second bool arg adds it as a overlay on top of the chart
studies.forEach((study) => {
const asOverlay = study === 'Volume';
activeChart.createStudy(study, asOverlay);
activeChart.createStudy(study);
});
// Subscribe to interval changes so it can be persisted in chart settings
@@ -127,5 +126,7 @@ const getOverrides = (theme: 'dark' | 'light') => {
// colors set here, trading view lets the user set a color
'paneProperties.background': theme === 'dark' ? '#05060C' : '#fff',
'paneProperties.backgroundType': 'solid',
// hide market name within TV chart as its already above
'paneProperties.legendProperties.showSeriesTitle': false,
};
};
+70
View File
@@ -1986,8 +1986,14 @@ export type MarginLevels = {
initialLevel: Scalars['String'];
/** Minimal margin for the position to be maintained in the network (unsigned integer) */
maintenanceLevel: Scalars['String'];
/** Margin factor, only relevant for isolated margin mode, else 0 */
marginFactor: Scalars['String'];
/** Margin mode of the party, cross margin or isolated margin */
marginMode: MarginMode;
/** Market in which the margin is required for this party */
market: Market;
/** When in isolated margin, the required order margin level, otherwise, 0 */
orderMarginLevel: Scalars['String'];
/** The party for this margin */
party: Party;
/** If the margin is between maintenance and search, the network will initiate a collateral search, expressed as unsigned integer */
@@ -2010,8 +2016,14 @@ export type MarginLevelsUpdate = {
initialLevel: Scalars['String'];
/** Minimal margin for the position to be maintained in the network (unsigned integer) */
maintenanceLevel: Scalars['String'];
/** Margin factor, only relevant for isolated margin mode, else 0 */
marginFactor: Scalars['String'];
/** Margin mode of the party, cross margin or isolated margin */
marginMode: MarginMode;
/** Market in which the margin is required for this party */
marketId: Scalars['ID'];
/** When in isolated margin, the required order margin level, otherwise, 0 */
orderMarginLevel: Scalars['String'];
/** The party for this margin */
partyId: Scalars['ID'];
/** If the margin is between maintenance and search, the network will initiate a collateral search (unsigned integer) */
@@ -2020,6 +2032,13 @@ export type MarginLevelsUpdate = {
timestamp: Scalars['Timestamp'];
};
export enum MarginMode {
/** Party is in cross margin mode */
MARGIN_MODE_CROSS_MARGIN = 'MARGIN_MODE_CROSS_MARGIN',
/** Party is in isolated margin mode */
MARGIN_MODE_ISOLATED_MARGIN = 'MARGIN_MODE_ISOLATED_MARGIN'
}
/** Represents a product & associated parameters that can be traded on Vega, has an associated OrderBook and Trade history */
export type Market = {
__typename?: 'Market';
@@ -3118,6 +3137,8 @@ export enum OrderRejectionReason {
ORDER_ERROR_INVALID_TIME_IN_FORCE = 'ORDER_ERROR_INVALID_TIME_IN_FORCE',
/** Invalid type */
ORDER_ERROR_INVALID_TYPE = 'ORDER_ERROR_INVALID_TYPE',
/** Party has insufficient funds to cover for the order margin for the new or amended order */
ORDER_ERROR_ISOLATED_MARGIN_CHECK_FAILED = 'ORDER_ERROR_ISOLATED_MARGIN_CHECK_FAILED',
/** Margin check failed - not enough available margin */
ORDER_ERROR_MARGIN_CHECK_FAILED = 'ORDER_ERROR_MARGIN_CHECK_FAILED',
/** Market is closed */
@@ -3138,6 +3159,8 @@ export enum OrderRejectionReason {
ORDER_ERROR_OFFSET_MUST_BE_GREATER_THAN_ZERO = 'ORDER_ERROR_OFFSET_MUST_BE_GREATER_THAN_ZERO',
/** Order is out of sequence */
ORDER_ERROR_OUT_OF_SEQUENCE = 'ORDER_ERROR_OUT_OF_SEQUENCE',
/** Pegged orders are not allowed for a party in isolated margin mode */
ORDER_ERROR_PEGGED_ORDERS_NOT_ALLOWED_IN_ISOLATED_MARGIN_MODE = 'ORDER_ERROR_PEGGED_ORDERS_NOT_ALLOWED_IN_ISOLATED_MARGIN_MODE',
/** A post-only order would produce an aggressive trade and thus it has been rejected */
ORDER_ERROR_POST_ONLY_ORDER_WOULD_TRADE = 'ORDER_ERROR_POST_ONLY_ORDER_WOULD_TRADE',
/** A reduce-ony order would not reduce the party's position and thus it has been rejected */
@@ -3586,6 +3609,41 @@ export type PartyLockedBalance = {
untilEpoch: Scalars['Int'];
};
/** Margin mode selected for the given party and market. */
export type PartyMarginMode = {
__typename?: 'PartyMarginMode';
/** Epoch at which the update happened. */
atEpoch: Scalars['Int'];
/** Selected margin mode. */
marginMode: MarginMode;
/** Margin factor for the market. Isolated mode only. */
margin_factor?: Maybe<Scalars['String']>;
/** Unique ID of the market. */
marketId: Scalars['ID'];
/** Maximum theoretical leverage for the market. Isolated mode only. */
max_theoretical_leverage?: Maybe<Scalars['String']>;
/** Minimum theoretical margin factor for the market. Isolated mode only. */
min_theoretical_margin_factor?: Maybe<Scalars['String']>;
/** Unique ID of the party. */
partyId: Scalars['ID'];
};
/** Edge type containing the deposit and cursor information returned by a PartyMarginModeConnection */
export type PartyMarginModeEdge = {
__typename?: 'PartyMarginModeEdge';
cursor: Scalars['String'];
node: PartyMarginMode;
};
/** Connection type for retrieving cursor-based paginated party margin modes information */
export type PartyMarginModesConnection = {
__typename?: 'PartyMarginModesConnection';
/** The party margin modes */
edges?: Maybe<Array<Maybe<PartyMarginModeEdge>>>;
/** The pagination information */
pageInfo?: Maybe<PageInfo>;
};
/**
* All staking information related to a Party.
* Contains the current recognised balance by the network and
@@ -4438,6 +4496,12 @@ export type Query = {
partiesConnection?: Maybe<PartyConnection>;
/** An entity that is trading on the Vega network */
party?: Maybe<Party>;
/**
* List margin modes per party per market
*
* Get a list of all margin modes, or for a specific market ID, or party ID.
*/
partyMarginModes?: Maybe<PartyMarginModesConnection>;
/** Fetch all positions */
positions?: Maybe<PositionConnection>;
/** A governance proposal located by either its ID or reference. If both are set, ID is used. */
@@ -6211,6 +6275,8 @@ export enum TransferType {
TRANSFER_TYPE_INFRASTRUCTURE_FEE_DISTRIBUTE = 'TRANSFER_TYPE_INFRASTRUCTURE_FEE_DISTRIBUTE',
/** Infrastructure fee paid from general account */
TRANSFER_TYPE_INFRASTRUCTURE_FEE_PAY = 'TRANSFER_TYPE_INFRASTRUCTURE_FEE_PAY',
/** Funds moved from order margin account to margin account. */
TRANSFER_TYPE_ISOLATED_MARGIN_LOW = 'TRANSFER_TYPE_ISOLATED_MARGIN_LOW',
/** Allocates liquidity fee earnings to each liquidity provider's network controlled liquidity fee account. */
TRANSFER_TYPE_LIQUIDITY_FEE_ALLOCATE = 'TRANSFER_TYPE_LIQUIDITY_FEE_ALLOCATE',
/** Liquidity fee received into general account */
@@ -6237,6 +6303,10 @@ export enum TransferType {
TRANSFER_TYPE_MTM_LOSS = 'TRANSFER_TYPE_MTM_LOSS',
/** Funds added to margin account after mark to market gain */
TRANSFER_TYPE_MTM_WIN = 'TRANSFER_TYPE_MTM_WIN',
/** Funds released from order margin account to general. */
TRANSFER_TYPE_ORDER_MARGIN_HIGH = 'TRANSFER_TYPE_ORDER_MARGIN_HIGH',
/** Funds moved from general account to order margin account. */
TRANSFER_TYPE_ORDER_MARGIN_LOW = 'TRANSFER_TYPE_ORDER_MARGIN_LOW',
/** Funds deducted from margin account after a perpetuals funding loss. */
TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS = 'TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS',
/** Funds added to margin account after a perpetuals funding gain. */
@@ -15,7 +15,7 @@ const Template: ComponentStory<typeof Popover> = (args) => {
<div>
<Popover
open={open}
onChange={setOpen}
onOpenChange={setOpen}
trigger={<Button variant="primary">Trigger</Button>}
>
{args.children}
@@ -4,29 +4,29 @@ export interface PopoverProps extends PopoverPrimitive.PopoverProps {
trigger: React.ReactNode | string;
children: React.ReactNode;
open?: boolean;
onChange?: (open: boolean) => void;
sideOffset?: number;
alignOffset?: number;
sideOffset?: PopoverPrimitive.PopperContentProps['sideOffset'];
alignOffset?: PopoverPrimitive.PopperContentProps['alignOffset'];
align?: PopoverPrimitive.PopperContentProps['align'];
}
export const Popover = ({
trigger,
children,
open,
onChange,
sideOffset = 17,
alignOffset = 0,
align = 'start',
...props
}: PopoverProps) => {
return (
<PopoverPrimitive.Root open={open} onOpenChange={(x) => onChange?.(x)}>
<PopoverPrimitive.Root {...props}>
<PopoverPrimitive.Trigger data-testid="popover-trigger">
{trigger}
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-testid="popover-content"
align="start"
className="rounded bg-vega-clight-800 dark:bg-vega-cdark-800 text-default border border-default"
align={align}
className="rounded bg-vega-clight-700 dark:bg-vega-cdark-700 text-default border border-vega-clight-500 dark:border-vega-cdark-500"
sideOffset={sideOffset}
alignOffset={alignOffset}
>
+30 -10
View File
@@ -7,6 +7,9 @@ import {
import classNames from 'classnames';
import type { ReactElement, ReactNode } from 'react';
import { Children, isValidElement, useRef, useState } from 'react';
import { VegaIcon } from '../icon/vega-icons/vega-icon';
import { VegaIconNames } from '../icon/vega-icons/vega-icon-record';
import { Popover } from '../popover/popover';
export interface TabsProps extends TabsPrimitive.TabsProps {
children: (ReactElement<TabProps> | null)[];
}
@@ -18,12 +21,9 @@ export const Tabs = ({
onValueChange,
...props
}: TabsProps) => {
const [activeTab, setActiveTab] = useState<string | undefined>(() => {
if (defaultValue) {
return defaultValue;
}
return children.find((v) => v)?.props.id;
});
const [activeTab, setActiveTab] = useState<string | undefined>(
() => value || defaultValue || children.find((v) => v)?.props.id
);
// Bunch of refs in order to detect wrapping in side the tabs so that we
// can apply a bg color
@@ -42,8 +42,13 @@ export const Tabs = ({
<TabsPrimitive.Root
{...props}
value={value || activeTab}
onValueChange={onValueChange || setActiveTab}
className="h-full grid grid-rows-[min-content_1fr]"
onValueChange={(value) => {
setActiveTab(value);
if (onValueChange) {
onValueChange(value);
}
}}
className="h-full grid grid-rows-[min-content_1fr] relative"
>
<div
ref={wrapperRef}
@@ -87,7 +92,7 @@ export const Tabs = ({
</TabsPrimitive.List>
<div
ref={menuRef}
className={classNames('flex-1 p-1', {
className={classNames('flex justify-end flex-1 p-1', {
'bg-vega-clight-700 dark:bg-vega-cdark-700': wrapped,
})}
>
@@ -101,12 +106,26 @@ export const Tabs = ({
})}
>
{child.props.menu}
{isValidElement(child.props.settings) && (
<Popover
align="end"
trigger={
<span className="flex items-center justify-center h-6 w-6">
<VegaIcon name={VegaIconNames.COG} size={16} />
</span>
}
>
<div className="p-2 lg:p-4 lg:min-w-[290px] flex justify-end">
{child.props.settings}
</div>
</Popover>
)}
</TabsPrimitive.Content>
);
})}
</div>
</div>
<div className="h-full overflow-auto">
<div className="relative h-full overflow-auto">
{Children.map(children, (child) => {
if (!isValidElement(child) || child.props.hidden) return null;
return (
@@ -134,6 +153,7 @@ interface TabProps {
hidden?: boolean;
overflowHidden?: boolean;
menu?: ReactNode;
settings?: ReactNode;
}
export const Tab = ({ children, ...props }: TabProps) => {
@@ -448,7 +448,24 @@ export type CreateReferralSet = {
};
};
export enum MarginMode {
/** Party is in cross margin mode */
MARGIN_MODE_CROSS_MARGIN = 1,
/** Party is in isolated margin mode */
MARGIN_MODE_ISOLATED_MARGIN = 'MARGIN_MODE_ISOLATED_MARGIN',
}
export interface UpdateMarginMode {
market_id: string;
mode: MarginMode;
marginFactor?: string;
}
export interface UpdateMarginModeBody {
updateMarginMode: UpdateMarginMode;
}
export type Transaction =
| UpdateMarginModeBody
| StopOrdersSubmissionBody
| StopOrdersCancellationBody
| OrderSubmissionBody
@@ -465,6 +482,10 @@ export type Transaction =
| ApplyReferralCode
| CreateReferralSet;
export const isMarginModeUpdateTransaction = (
transaction: Transaction
): transaction is UpdateMarginModeBody => 'updateMarginMode' in transaction;
export const isWithdrawTransaction = (
transaction: Transaction
): transaction is WithdrawSubmissionBody => 'withdrawSubmission' in transaction;
@@ -10,6 +10,7 @@ import {
isStopOrdersSubmissionTransaction,
isStopOrdersCancellationTransaction,
determineId,
isMarginModeUpdateTransaction,
} from '@vegaprotocol/wallet';
import { create } from 'zustand';
@@ -58,7 +59,7 @@ export interface VegaTransactionStore {
export const useVegaTransactionStore = create<VegaTransactionStore>()(
subscribeWithSelector((set, get) => ({
transactions: [] as VegaStoredTxState[],
transactions: [] as (VegaStoredTxState | undefined)[],
create: (body: Transaction, order?: OrderTxUpdateFieldsFragment) => {
const transactions = get().transactions;
const now = new Date();
@@ -205,16 +206,23 @@ export const useVegaTransactionStore = create<VegaTransactionStore>()(
isStopOrdersCancellationTransaction(transaction.body);
const isConfirmedStopOrderSubmission =
isStopOrdersSubmissionTransaction(transaction.body);
const isConfirmedMarginModeTransaction =
isMarginModeUpdateTransaction(transaction.body);
if (
(isConfirmedOrderCancellation ||
isConfirmedTransfer ||
isConfirmedStopOrderCancellation ||
isConfirmedStopOrderSubmission) &&
!transactionResult.error &&
transactionResult.status
isConfirmedOrderCancellation ||
isConfirmedTransfer ||
isConfirmedStopOrderCancellation ||
isConfirmedStopOrderSubmission ||
isConfirmedMarginModeTransaction
//transactionResult.status
) {
transaction.status = VegaTxStatus.Complete;
if (transactionResult.error) {
transaction.status = VegaTxStatus.Error;
transaction.error = new Error(transactionResult.error);
} else {
transaction.status = VegaTxStatus.Complete;
}
}
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
@@ -7,6 +7,7 @@ import type {
OrderSubmission,
StopOrdersSubmission,
StopOrderSetup,
UpdateMarginMode,
} from '@vegaprotocol/wallet';
import type {
OrderTxUpdateFieldsFragment,
@@ -26,6 +27,8 @@ import {
isStopOrdersSubmissionTransaction,
isStopOrdersCancellationTransaction,
isReferralRelatedTransaction,
isMarginModeUpdateTransaction,
MarginMode,
} from '@vegaprotocol/wallet';
import { useVegaTransactionStore } from './use-vega-transaction-store';
import { VegaTxStatus } from './types';
@@ -163,6 +166,7 @@ const isClosePositionTransaction = (tx: VegaStoredTxState) => {
};
const isTransactionTypeSupported = (tx: VegaStoredTxState) => {
const marginModeUpdate = isMarginModeUpdateTransaction(tx.body);
const withdraw = isWithdrawTransaction(tx.body);
const submitOrder = isOrderSubmissionTransaction(tx.body);
const cancelOrder = isOrderCancellationTransaction(tx.body);
@@ -173,6 +177,7 @@ const isTransactionTypeSupported = (tx: VegaStoredTxState) => {
const transfer = isTransferTransaction(tx.body);
const referral = isReferralRelatedTransaction(tx.body);
return (
marginModeUpdate ||
withdraw ||
submitOrder ||
cancelOrder ||
@@ -445,6 +450,27 @@ const CancelOrderDetails = ({
);
};
const MarginModeDetails = ({ data }: { data: UpdateMarginMode }) => {
const t = useT();
const { data: markets } = useMarketsMapProvider();
const marketId = data.market_id;
const market = marketId && markets?.[marketId];
if (!market) {
return null;
}
return (
<Panel>
<h4>{t('Update margin mode')}</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
{data.mode === MarginMode.MARGIN_MODE_CROSS_MARGIN
? t('Cross margin mode')
: t('Isolated margin mode {{leverage}}x', {
leverage: 1 / Number(data.marginFactor),
})}
</Panel>
);
};
const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
const t = useT();
const formatTrigger = useFormatTrigger();
@@ -598,6 +624,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
);
}
if (isMarginModeUpdateTransaction(tx.body)) {
return <MarginModeDetails data={tx.body.updateMarginMode} />;
}
if (isClosePositionTransaction(tx)) {
const transaction = tx.body as BatchMarketInstructionSubmissionBody;
const marketId = first(
+2 -2
View File
@@ -49,8 +49,8 @@
"@web3-react/metamask": "^8.1.2-beta.0",
"@web3-react/walletconnect": "8.1.3-beta.0",
"@web3-react/walletconnect-v2": "^8.1.3-beta.0",
"ag-grid-community": "^29.3.5",
"ag-grid-react": "^29.3.5",
"ag-grid-community": "^31.0.1",
"ag-grid-react": "^31.0.1",
"allotment": "1.19.2",
"alpha-lyrae": "vegaprotocol/alpha-lyrae",
"apollo-link-timeout": "^4.0.0",
+9 -8
View File
@@ -8609,16 +8609,17 @@ aes-js@^3.1.2:
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
ag-grid-community@^29.3.5:
version "29.3.5"
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-29.3.5.tgz#16897896d10fa3ecac79279aad50d3aaa17c5f33"
integrity sha512-LxUo21f2/CH31ACEs1C7Q/ggGGI1fQPSTB4aY5OThmM+lBkygZ7QszBE8jpfgWOIjvjdtcdIeQbmbjkHeMsA7A==
ag-grid-community@^31.0.1, ag-grid-community@~31.0.1:
version "31.0.1"
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-31.0.1.tgz#26022b29a7b90a0515076837d630ac9cd24cf28d"
integrity sha512-RZQlW1DTOJHsUR/tnbnTJQKgAnDlHi05YYyTe5AgNor/1TlX1hoYdcqrGsJjvcHQgTjeEgzWOL0yf+KcqXZzxg==
ag-grid-react@^29.3.5:
version "29.3.5"
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-29.3.5.tgz#0eae8934d372c7751e98789542fc663aee0ad6ad"
integrity sha512-Eg0GJ8hEBuxdVaN5g+qITOzhw0MGL9avL0Oaajr+p7QRtq2pIFHLZSknWsCBzUTjidiu75WZMKwlZjtGEuafdQ==
ag-grid-react@^31.0.1:
version "31.0.1"
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-31.0.1.tgz#c7e3cf029ea1b97ab7f1d5134c8bb0086b4d2aac"
integrity sha512-9nmYPsgH1YUDUDOTiyaFsysoNAx/y72ovFJKuOffZC1V7OrQMadyP6DbqGFWCqzzoLJOY7azOr51dDQzAIXLpw==
dependencies:
ag-grid-community "~31.0.1"
prop-types "^15.8.1"
agent-base@5: