Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f23a09810 | ||
|
|
13b1269a58 | ||
|
|
398cd9e639 | ||
|
|
d675410a78 | ||
|
|
afe85ae622 | ||
|
|
c6df34fe95 | ||
|
|
c7d0803164 | ||
|
|
250a654544 | ||
|
|
7f5e8ebb15 | ||
|
|
ccbe34e172 | ||
|
|
27e8ce88bf | ||
|
|
41804d9869 | ||
|
|
293288286b | ||
|
|
a77765b1e9 | ||
|
|
2453d7841a |
+12
-11
@@ -1,27 +1,28 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -16,14 +16,14 @@ NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.21.0-core-0.72.14
|
||||
NX_APP_VERSION=v0.21.1-core-0.72.14
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -22,7 +22,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -69,15 +69,15 @@ export const MarketPage = () => {
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId) {
|
||||
update({ marketId: data.id });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id]);
|
||||
|
||||
// Make sidebar open on deal ticket by default
|
||||
useEffect(() => {
|
||||
if (init && view === null) {
|
||||
// make sidebar open on market id change
|
||||
setView({ type: ViewType.Order });
|
||||
}
|
||||
}, [init, view, setView]);
|
||||
|
||||
// make sidebar open on deal ticket by default
|
||||
if (view === null) {
|
||||
setView({ type: ViewType.Order });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id, setView, init, view]);
|
||||
|
||||
const tradeView = useMemo(() => {
|
||||
if (largeScreen) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { act, render, screen, within, waitFor } from '@testing-library/react';
|
||||
import { act, render, screen, within } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Closed } from './closed';
|
||||
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
|
||||
@@ -10,55 +10,21 @@ import type {
|
||||
OracleSpecDataConnectionQuery,
|
||||
MarketsDataQuery,
|
||||
MarketsQuery,
|
||||
SuccessorMarketIdsQuery,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
OracleSpecDataConnectionDocument,
|
||||
MarketsDataDocument,
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
createMarketFragment,
|
||||
marketsQuery,
|
||||
marketsDataQuery,
|
||||
createMarketsDataFragment,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { FeatureFlags } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
...jest.requireActual('@vegaprotocol/markets'),
|
||||
useSuccessorMarket: (marketId: string) =>
|
||||
marketId === 'include-0'
|
||||
? {
|
||||
data: {
|
||||
id: 'successorMarketID',
|
||||
state: 'STATE_ACTIVE',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: { data: undefined },
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
} as FeatureFlags,
|
||||
};
|
||||
});
|
||||
|
||||
describe('Closed', () => {
|
||||
let originalNow: typeof Date.now;
|
||||
@@ -218,10 +184,8 @@ describe('Closed', () => {
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const expectedHeaders = [
|
||||
'Market',
|
||||
'Description',
|
||||
'Status',
|
||||
'Settlement date',
|
||||
'Successor market',
|
||||
'Best bid',
|
||||
'Best offer',
|
||||
'Mark price',
|
||||
@@ -235,10 +199,8 @@ describe('Closed', () => {
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
market.tradableInstrument.instrument.code,
|
||||
market.tradableInstrument.instrument.name,
|
||||
MarketStateMapping[market.state],
|
||||
'3 days ago',
|
||||
'-',
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
addDecimalsFormatNumber(marketsData.bestBidPrice, market.decimalPlaces),
|
||||
addDecimalsFormatNumber(
|
||||
@@ -340,43 +302,26 @@ describe('Closed', () => {
|
||||
.getAllByRole('gridcell')
|
||||
.filter((cell) => cell.getAttribute('col-id') === 'code')
|
||||
.map((cell) => {
|
||||
const marketId = within(cell)
|
||||
.getByTestId('market-code')
|
||||
.getAttribute('data-market-id');
|
||||
return marketId;
|
||||
const marketCode = within(cell).getByTestId('stack-cell-primary');
|
||||
return marketCode.textContent;
|
||||
});
|
||||
expect(cells).toEqual(expectedRows.map((m) => m.node.id));
|
||||
expect(cells).toEqual(
|
||||
expectedRows.map((m) => m.node.tradableInstrument.instrument.code)
|
||||
);
|
||||
});
|
||||
|
||||
it('successor marked should be visible', async () => {
|
||||
const mixedMarkets = [
|
||||
const marketsWithSuccessorID = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: createMarketFragment({
|
||||
id: 'include-0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
successorMarketID: 'successor',
|
||||
}),
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: {
|
||||
...createMarketFragment({
|
||||
id: 'successorMarketID',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
}),
|
||||
tradableInstrument: {
|
||||
...createMarketFragment().tradableInstrument,
|
||||
instrument: {
|
||||
...createMarketFragment().tradableInstrument.instrument,
|
||||
id: 'successorAssset',
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||
const mockWithSuccessors: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
@@ -384,42 +329,17 @@ describe('Closed', () => {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: mixedMarkets,
|
||||
edges: marketsWithSuccessorID,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'include-0',
|
||||
successorMarketID: 'successorMarketID',
|
||||
parentMarketID: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await act(() => {
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
mocks={[mockWithSuccessors, marketsDataMock, oracleDataMock]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
@@ -431,122 +351,15 @@ describe('Closed', () => {
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('button', { name: /^SuccessorCode/ })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
screen
|
||||
.getAllByRole('gridcell', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
.forEach((element) => {
|
||||
expect(element.querySelector('[title="Future"]')?.textContent).toEqual(
|
||||
'Futr'
|
||||
);
|
||||
});
|
||||
});
|
||||
const container = within(
|
||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||
);
|
||||
const cell = container.getAllByRole('gridcell', {
|
||||
name: (_name, element) => element.getAttribute('col-id') === 'code',
|
||||
})[0];
|
||||
|
||||
it('feature flag should hide successors', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
|
||||
const mixedMarkets = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: createMarketFragment({
|
||||
id: 'include-0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
}),
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: {
|
||||
...createMarketFragment({
|
||||
id: 'successorMarketID',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
}),
|
||||
tradableInstrument: {
|
||||
...createMarketFragment().tradableInstrument,
|
||||
instrument: {
|
||||
...createMarketFragment().tradableInstrument.instrument,
|
||||
id: 'successorAssset',
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: mixedMarkets,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'include-0',
|
||||
successorMarketID: 'successorMarketID',
|
||||
parentMarketID: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Closed />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'settlementDate',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('successorMarket');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,10 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
COL_DEFS,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { ProductType } from '@vegaprotocol/types';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
@@ -20,17 +17,13 @@ import type {
|
||||
DataSourceFilterFragment,
|
||||
MarketMaybeWithData,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
MarketActionsDropdown,
|
||||
closedMarketsWithDataProvider,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { closedMarketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { SettlementDateCell } from './settlement-date-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
|
||||
type SettlementAsset =
|
||||
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||
@@ -51,7 +44,9 @@ interface Row {
|
||||
setlementDataSourceFilter: DataSourceFilterFragment | undefined;
|
||||
tradingTerminationOracleId: string;
|
||||
settlementAsset: SettlementAsset;
|
||||
productType: string;
|
||||
productType: ProductType | undefined;
|
||||
successorMarketID: string | null | undefined;
|
||||
parentMarketID: string | null | undefined;
|
||||
}
|
||||
|
||||
export const Closed = () => {
|
||||
@@ -95,16 +90,19 @@ export const Closed = () => {
|
||||
tradingTerminationOracleId:
|
||||
instrument.product.dataSourceSpecForTradingTermination.id,
|
||||
settlementAsset: instrument.product.settlementAsset,
|
||||
productType: instrument.product.__typename || '',
|
||||
productType: instrument.product.__typename,
|
||||
successorMarketID: market.successorMarketID,
|
||||
parentMarketID: market.parentMarketID,
|
||||
};
|
||||
|
||||
return row;
|
||||
});
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<ClosedMarketsDataGrid rowData={rowData} error={error} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return <ClosedMarketsDataGrid rowData={rowData} error={error} />;
|
||||
};
|
||||
|
||||
const components = {
|
||||
MarketCodeCell,
|
||||
};
|
||||
|
||||
const ClosedMarketsDataGrid = ({
|
||||
@@ -117,15 +115,11 @@ const ClosedMarketsDataGrid = ({
|
||||
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const cols: ColDef[] = compact([
|
||||
return [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'name',
|
||||
cellRenderer: 'MarketCodeCell',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
@@ -176,12 +170,6 @@ const ClosedMarketsDataGrid = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
field: 'id',
|
||||
colId: 'successorMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
headerName: t('Best bid'),
|
||||
field: 'bestBidPrice',
|
||||
@@ -263,12 +251,13 @@ const ClosedMarketsDataGrid = ({
|
||||
<MarketActionsDropdown
|
||||
marketId={data.id}
|
||||
assetId={data.settlementAsset.id}
|
||||
successorMarketID={data.successorMarketID}
|
||||
parentMarketID={data.parentMarketID}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]);
|
||||
return cols;
|
||||
];
|
||||
}, [openAssetDialog]);
|
||||
|
||||
return (
|
||||
@@ -276,8 +265,9 @@ const ClosedMarketsDataGrid = ({
|
||||
rowData={rowData}
|
||||
columnDefs={colDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ SuccessorMarketRenderer, MarketNameCell }}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
components={components}
|
||||
rowHeight={45}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProductTypeShortName } from '@vegaprotocol/types';
|
||||
import type { MarketCodeCellProps } from './market-code-cell';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
|
||||
describe('MarketCodeCell', () => {
|
||||
const renderComponent = (props: MarketCodeCellProps) => {
|
||||
return render(<MarketCodeCell {...props} />);
|
||||
};
|
||||
|
||||
it('renders SCCR if the market is a successor', () => {
|
||||
const productType = 'Future';
|
||||
const code = 'code';
|
||||
const props = {
|
||||
value: 'code',
|
||||
data: {
|
||||
productType,
|
||||
parentMarketID: 'foo',
|
||||
successorMarketID: undefined,
|
||||
},
|
||||
} as const;
|
||||
renderComponent(props);
|
||||
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
ProductTypeShortName[productType]
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'SCCR'
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).not.toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders PRNT if the market is a parent', () => {
|
||||
const productType = 'Future';
|
||||
const code = 'code';
|
||||
const props = {
|
||||
value: 'code',
|
||||
data: {
|
||||
productType,
|
||||
parentMarketID: undefined,
|
||||
successorMarketID: 'foo',
|
||||
},
|
||||
} as const;
|
||||
renderComponent(props);
|
||||
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
ProductTypeShortName[productType]
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).not.toHaveTextContent(
|
||||
'SCCR'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders both SCCR and PRNT if the market is both a parent and a successor', () => {
|
||||
const productType = 'Future';
|
||||
const code = 'code';
|
||||
const props = {
|
||||
value: 'code',
|
||||
data: {
|
||||
productType,
|
||||
parentMarketID: 'foo',
|
||||
successorMarketID: 'bar',
|
||||
},
|
||||
} as const;
|
||||
renderComponent(props);
|
||||
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
ProductTypeShortName[productType]
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'SCCR'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import compact from 'lodash/compact';
|
||||
import type { ProductType } from '@vegaprotocol/types';
|
||||
import { ProductTypeMapping, ProductTypeShortName } from '@vegaprotocol/types';
|
||||
import { StackedCell } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export interface MarketCodeCellProps {
|
||||
value: string | undefined; // market code
|
||||
data: {
|
||||
productType: ProductType | undefined;
|
||||
parentMarketID: string | null | undefined;
|
||||
successorMarketID: string | null | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export const MarketCodeCell = ({ value, data }: MarketCodeCellProps) => {
|
||||
if (!value || !data || !data.productType) return null;
|
||||
|
||||
const infoSpanClasses =
|
||||
'mr-1 pr-1 uppercase border-r last:pr-0 last:mr-0 last:border-r-0 border-vega-clight-200 dark:border-vega-cdark-200';
|
||||
|
||||
const info = compact([
|
||||
<span
|
||||
className={infoSpanClasses}
|
||||
key="productType"
|
||||
title={ProductTypeMapping[data.productType]}
|
||||
>
|
||||
{ProductTypeShortName[data.productType]}
|
||||
</span>,
|
||||
data.parentMarketID && (
|
||||
<span
|
||||
className={infoSpanClasses}
|
||||
key="successor"
|
||||
title={t('Successor of a market')}
|
||||
>
|
||||
{t('SCCR')}
|
||||
</span>
|
||||
),
|
||||
data.successorMarketID && (
|
||||
<span
|
||||
className={infoSpanClasses}
|
||||
key="parent"
|
||||
title={t('Parent of a market')}
|
||||
>
|
||||
{t('PRNT')}
|
||||
</span>
|
||||
),
|
||||
]);
|
||||
|
||||
return <StackedCell primary={value} secondary={info} />;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
|
||||
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||
|
||||
const defaultColDef = {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
|
||||
const components = {
|
||||
PriceFlashCell,
|
||||
};
|
||||
|
||||
type Props = TypedDataAgGrid<MarketMaybeWithData>;
|
||||
|
||||
export const MarketListTable = (props: Props) => {
|
||||
const columnDefs = useColumnDefs();
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
getRowId={getRowId}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
components={components}
|
||||
rowHeight={45}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketListTable;
|
||||
+27
@@ -9,14 +9,21 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { DApp, EXPLORER_MARKET, useLinks } from '@vegaprotocol/environment';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
export const MarketActionsDropdown = ({
|
||||
marketId,
|
||||
assetId,
|
||||
successorMarketID,
|
||||
parentMarketID,
|
||||
}: {
|
||||
marketId: string;
|
||||
assetId: string;
|
||||
successorMarketID: string | null | undefined;
|
||||
parentMarketID: string | null | undefined;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const open = useAssetDetailsDialogStore((store) => store.open);
|
||||
const linkCreator = useLinks(DApp.Explorer);
|
||||
|
||||
@@ -42,6 +49,26 @@ export const MarketActionsDropdown = ({
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View settlement asset details')}
|
||||
</TradingDropdownItem>
|
||||
{parentMarketID && (
|
||||
<TradingDropdownItem
|
||||
onClick={() => {
|
||||
navigate(Links[Routes.MARKET](parentMarketID));
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EYE} size={16} />
|
||||
{t('View parent market')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
{successorMarketID && (
|
||||
<TradingDropdownItem
|
||||
onClick={() => {
|
||||
navigate(Links[Routes.MARKET](successorMarketID));
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EYE} size={16} />
|
||||
{t('View successor market')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
</ActionsDropdown>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Tab,
|
||||
TradingAnchorButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Markets } from './markets';
|
||||
import { OpenMarkets } from './open-markets';
|
||||
import { Proposed } from './proposed';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { Closed } from './closed';
|
||||
@@ -33,7 +33,7 @@ export const MarketsPage = () => {
|
||||
<div className="h-full my-1 border rounded-sm border-default">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<Markets />
|
||||
<OpenMarkets />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="proposed-markets"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { MarketsContainer } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Markets = () => {
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
return (
|
||||
<MarketsContainer
|
||||
onSelect={handleOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
|
||||
import { marketListProvider } from '@vegaprotocol/markets';
|
||||
import { useEffect } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import MarketListTable from './market-list-table';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { useYesterday } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const POLLING_TIME = 2000;
|
||||
|
||||
export const OpenMarkets = () => {
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
const yesterday = useYesterday();
|
||||
const { data, error, reload } = useDataProvider({
|
||||
dataProvider: marketListProvider,
|
||||
variables: {
|
||||
since: new Date(yesterday).toISOString(),
|
||||
interval: Interval.INTERVAL_I1H,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
reload();
|
||||
}, POLLING_TIME);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<MarketListTable
|
||||
rowData={data}
|
||||
onCellClicked={({
|
||||
data,
|
||||
column,
|
||||
event,
|
||||
}: CellClickedEvent<MarketMaybeWithData>) => {
|
||||
if (!data) return;
|
||||
|
||||
// prevent navigating to the market page if any of the below cells are clicked
|
||||
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
|
||||
const colId = column.getColId();
|
||||
|
||||
if (
|
||||
[
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
'market-actions',
|
||||
].includes(colId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore metaKey exists
|
||||
handleOnSelect(data.id, event ? event.metaKey : false);
|
||||
}}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
getMatchingOracleProvider,
|
||||
getVerifiedStatusIcon,
|
||||
useOracleProofs,
|
||||
} from '@vegaprotocol/markets';
|
||||
|
||||
export const OracleStatus = ({
|
||||
dataSourceSpecForSettlementData,
|
||||
dataSourceSpecForTradingTermination,
|
||||
}: Pick<
|
||||
Market['tradableInstrument']['instrument']['product'],
|
||||
'dataSourceSpecForSettlementData' | 'dataSourceSpecForTradingTermination'
|
||||
>) => {
|
||||
const { ORACLE_PROOFS_URL } = useEnvironment();
|
||||
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
|
||||
|
||||
if (providers) {
|
||||
const settlementDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForSettlementData.data,
|
||||
providers
|
||||
);
|
||||
const tradingTerminationDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForTradingTermination.data,
|
||||
providers
|
||||
);
|
||||
let maliciousOracleProvider = null;
|
||||
|
||||
if (settlementDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = settlementDataProvider;
|
||||
} else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = tradingTerminationDataProvider;
|
||||
}
|
||||
|
||||
if (!maliciousOracleProvider) return null;
|
||||
|
||||
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
|
||||
|
||||
return <Icon size={3} name={icon as IconName} className="ml-1" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useMarketsMapProvider } from '@vegaprotocol/markets';
|
||||
|
||||
export const ParentMarketCell = ({
|
||||
value,
|
||||
}: {
|
||||
value: string; // parentMarketId
|
||||
}) => {
|
||||
const { data, loading } = useMarketsMapProvider();
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
if (!data || !data[value]) return <span>-</span>;
|
||||
|
||||
return <div>{data[value].tradableInstrument.instrument.code}</div>;
|
||||
};
|
||||
@@ -1,6 +1,10 @@
|
||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import { ParentMarketCell } from './parent-market-cell';
|
||||
|
||||
const cellRenderers = {
|
||||
ParentMarketCell,
|
||||
};
|
||||
|
||||
export const Proposed = () => {
|
||||
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
|
||||
return <ProposalsList cellRenderers={cellRenderers} />;
|
||||
};
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { render, screen, act, waitFor } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import {
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
|
||||
const mockSuccessorsQuery = [
|
||||
{
|
||||
id: 'market1',
|
||||
parentMarketID: 'parentMarket1',
|
||||
successorMarketID: 'successorMarket1',
|
||||
},
|
||||
{ id: 'market2', parentMarketID: 'parentMarket2' },
|
||||
{ id: 'market3', successorMarketID: 'successorMarket3' },
|
||||
];
|
||||
const parentMarket1 = {
|
||||
id: 'parentMarket1',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code parent 1', id: '1' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const successorMarket1 = {
|
||||
id: 'successorMarket1',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code successor 1', id: '2' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const parentMarket2 = {
|
||||
id: 'parentMarket2',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code parent 2', id: '3' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const successorMarket3 = {
|
||||
id: 'successorMarket3',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code successor 3', id: '4' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
|
||||
const mockMarkets = [
|
||||
parentMarket1,
|
||||
successorMarket1,
|
||||
parentMarket2,
|
||||
successorMarket3,
|
||||
];
|
||||
|
||||
const mockClickHandler = jest.fn();
|
||||
jest.mock('../../lib/hooks/use-market-click-handler', () => ({
|
||||
useMarketClickHandler: jest.fn().mockImplementation(() => mockClickHandler),
|
||||
}));
|
||||
|
||||
const marketMock = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
variables: undefined,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: mockMarkets.map((item) => ({
|
||||
node: {
|
||||
...createMarketFragment(item),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const successorMock = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: mockSuccessorsQuery.map((item) => ({
|
||||
node: {
|
||||
...item,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mocks = [marketMock, successorMock];
|
||||
|
||||
describe('SuccessorMarketRenderer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should properly rendered successor market', async () => {
|
||||
const successorValue = 'market1';
|
||||
render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code successor 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket1', false);
|
||||
});
|
||||
});
|
||||
it('should properly rendered parent market', async () => {
|
||||
const successorValue = 'market1';
|
||||
render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code parent 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket1', false);
|
||||
});
|
||||
});
|
||||
it('should properly rendered only parent market', async () => {
|
||||
const successorValue = 'market2';
|
||||
const { rerender } = render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code parent 2')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket2', false);
|
||||
});
|
||||
|
||||
rerender(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
it('should properly rendered only successor market', async () => {
|
||||
const successorValue = 'market3';
|
||||
const { rerender } = render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code successor 3')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket3', false);
|
||||
});
|
||||
|
||||
await act(() => {
|
||||
rerender(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { MarketNameCell } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketProvider, useSuccessorMarketIds } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const SuccessorMarketRenderer = ({
|
||||
value,
|
||||
parent,
|
||||
}: {
|
||||
value: string;
|
||||
parent?: boolean;
|
||||
}) => {
|
||||
const successors = useSuccessorMarketIds(value);
|
||||
const onMarketClick = useMarketClickHandler();
|
||||
|
||||
const lookupValue = successors
|
||||
? parent
|
||||
? successors.parentMarketID
|
||||
: successors.successorMarketID
|
||||
: '';
|
||||
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: marketProvider,
|
||||
variables: {
|
||||
marketId: lookupValue || '',
|
||||
},
|
||||
skip: !lookupValue,
|
||||
});
|
||||
|
||||
return data ? (
|
||||
<MarketNameCell
|
||||
value={data.tradableInstrument.instrument.code}
|
||||
data={data}
|
||||
onMarketClick={onMarketClick}
|
||||
productType={data.tradableInstrument.instrument?.product.__typename}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,226 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef, ValueFormatterParams } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type {
|
||||
MarketMaybeWithData,
|
||||
MarketMaybeWithDataAndCandles,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
import { calcCandleVolume } from '@vegaprotocol/markets';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
|
||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
|
||||
export const useColumnDefs = () => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
flex: 2,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.code'
|
||||
>) => (
|
||||
<MarketCodeCell
|
||||
value={value}
|
||||
data={{
|
||||
productType:
|
||||
data?.tradableInstrument.instrument.product.__typename,
|
||||
successorMarketID: data?.successorMarketID,
|
||||
parentMarketID: data?.parentMarketID,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
headerName: t('Trading mode'),
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return '-';
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
|
||||
const withTriggerInfo =
|
||||
marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED;
|
||||
|
||||
if (withTriggerInfo) {
|
||||
return (
|
||||
<Tooltip
|
||||
description={`${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`}
|
||||
>
|
||||
<span>
|
||||
{Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return Schema.MarketTradingModeMapping[marketTradingMode];
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketTradingModeMapping,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketStateMapping,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Mark price'),
|
||||
field: 'data.markPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
|
||||
},
|
||||
{
|
||||
headerName: t('24h volume'),
|
||||
type: 'rightAligned',
|
||||
field: 'data.candles',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithDataAndCandles>) => {
|
||||
if (!data) return 0;
|
||||
const candles = data?.candles;
|
||||
const vol = candles ? calcCandleVolume(candles) : '0';
|
||||
return Number(vol);
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: ValueFormatterParams<MarketMaybeWithDataAndCandles, 'candles'>) => {
|
||||
const candles = data?.candles;
|
||||
const vol = candles ? calcCandleVolume(candles) : '0';
|
||||
const volume =
|
||||
data && vol && vol !== '0'
|
||||
? addDecimalsFormatNumber(vol, data.positionDecimalPlaces)
|
||||
: '0.00';
|
||||
return volume;
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||
>) => {
|
||||
const value =
|
||||
data?.tradableInstrument.instrument.product.settlementAsset;
|
||||
return value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{value.symbol}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Spread'),
|
||||
field: 'data.bestBidPrice',
|
||||
type: 'rightAligned',
|
||||
filter: 'agNumberColumnFilter',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
if (!data || !data.data?.bestOfferPrice || !data.data?.bestBidPrice) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const offer = toBigNum(data.data.bestOfferPrice, data.decimalPlaces);
|
||||
const bid = toBigNum(data.data.bestBidPrice, data.decimalPlaces);
|
||||
|
||||
const spread = offer.minus(bid).toNumber();
|
||||
|
||||
// The calculation above can result in '-0' being rendered after formatting
|
||||
// so return Math.abs to remove it and just render '0'
|
||||
if (spread === 0) {
|
||||
return Math.abs(spread);
|
||||
}
|
||||
|
||||
return spread;
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestBidPrice'
|
||||
>) => {
|
||||
if (!value) return '-';
|
||||
return value.toString();
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'market-actions',
|
||||
field: 'id',
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<MarketActionsDropdown
|
||||
marketId={data.id}
|
||||
assetId={
|
||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||
}
|
||||
successorMarketID={data.successorMarketID}
|
||||
parentMarketID={data.parentMarketID}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[openAssetDetailsDialog]
|
||||
);
|
||||
};
|
||||
@@ -10,7 +10,7 @@ import type { SortType } from './sort-dropdown';
|
||||
import { SortTypeMapping } from './sort-dropdown';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { subDays } from 'date-fns';
|
||||
import { isMarketActive } from './use-market-selector-list';
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
|
||||
jest.mock('@vegaprotocol/markets');
|
||||
const mockUseMarketList = useMarketList as jest.Mock;
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
isMarketActive,
|
||||
useMarketSelectorList,
|
||||
} from './use-market-selector-list';
|
||||
import { Product } from '../../components/market-selector/product-selector';
|
||||
import { useMarketSelectorList } from './use-market-selector-list';
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
import { Product } from './product-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import type { Filter } from '../../components/market-selector';
|
||||
import type { Filter } from './market-selector';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
import type { Filter } from '../../components/market-selector/market-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { Product } from './product-selector';
|
||||
|
||||
// Used for sort order and filter
|
||||
const MARKET_TEMPLATE = [
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketState.STATE_SUSPENDED,
|
||||
MarketState.STATE_PENDING,
|
||||
];
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
|
||||
export const useMarketSelectorList = ({
|
||||
product,
|
||||
@@ -87,7 +80,3 @@ export const useMarketSelectorList = ({
|
||||
|
||||
return { markets, data, loading, error, reload };
|
||||
};
|
||||
|
||||
export const isMarketActive = (state: MarketState) => {
|
||||
return MARKET_TEMPLATE.includes(state);
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('Navbar', () => {
|
||||
['/markets/all', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
[expect.stringContaining('governance'), 'Governance'],
|
||||
];
|
||||
|
||||
const links = screen.getAllByRole('link');
|
||||
@@ -88,6 +89,7 @@ describe('Navbar', () => {
|
||||
['/markets/all', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
[expect.stringContaining('governance'), 'Governance'],
|
||||
];
|
||||
const links = menu.getAllByRole('link');
|
||||
links.forEach((link, i) => {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { ButtonHTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEnvironment, DocsLinks, Networks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
useEnvironment,
|
||||
DocsLinks,
|
||||
Networks,
|
||||
DApp,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||
@@ -59,14 +65,14 @@ export const Navbar = ({
|
||||
<VLogo className="w-4" />
|
||||
</NavLink>
|
||||
{/* Left section */}
|
||||
<div className="lg:hidden flex items-center">{children}</div>
|
||||
<div className="flex items-center lg:hidden">{children}</div>
|
||||
{/* Used to show header in nav on mobile */}
|
||||
<div className="hidden lg:block">
|
||||
<NavbarMenu onClick={() => setMenu(null)} />
|
||||
</div>
|
||||
|
||||
{/* Right section */}
|
||||
<div className="ml-auto flex justify-end items-center gap-2">
|
||||
<div className="flex items-center justify-end ml-auto gap-2">
|
||||
<ProtocolUpgradeCountdown />
|
||||
<NavbarMobileButton
|
||||
onClick={() => {
|
||||
@@ -100,7 +106,7 @@ export const Navbar = ({
|
||||
onOpenChange={(open) => setMenu((x) => (open ? x : null))}
|
||||
>
|
||||
<D.Overlay
|
||||
className="lg:hidden fixed inset-0 dark:bg-black/80 bg-black/50 z-20"
|
||||
className="fixed inset-0 z-20 lg:hidden dark:bg-black/80 bg-black/50"
|
||||
data-testid="navbar-menu-overlay"
|
||||
/>
|
||||
<D.Content
|
||||
@@ -111,7 +117,7 @@ export const Navbar = ({
|
||||
)}
|
||||
data-testid="navbar-menu-content"
|
||||
>
|
||||
<div className="flex justify-end items-center h-10 p-1">
|
||||
<div className="flex items-center justify-end h-10 p-1">
|
||||
<NavbarMobileButton onClick={() => setMenu(null)}>
|
||||
<span className="sr-only">{t('Close menu')}</span>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={24} />
|
||||
@@ -179,6 +185,11 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
||||
{t('Portfolio')}
|
||||
</NavbarLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarLinkExternal to={useLinks(DApp.Token)()}>
|
||||
{t('Governance')}
|
||||
</NavbarLinkExternal>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
|
||||
<NavbarContent data-testid="navbar-content-resources">
|
||||
@@ -336,7 +347,7 @@ const NavbarLinkExternal = ({
|
||||
<NavLink
|
||||
to={to}
|
||||
className={classNames(
|
||||
'flex lg:inline-flex gap-2 justify-between items-center relative',
|
||||
'flex gap-2 lg:h-full items-center',
|
||||
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||
)}
|
||||
@@ -344,7 +355,7 @@ const NavbarLinkExternal = ({
|
||||
target="_blank"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={14} />
|
||||
</NavLink>
|
||||
</N.Link>
|
||||
);
|
||||
@@ -364,8 +375,8 @@ const BurgerIcon = () => (
|
||||
|
||||
const NavbarListDivider = () => {
|
||||
return (
|
||||
<div className="py-2 px-6 lg:px-0" role="separator">
|
||||
<div className="h-px lg:h-full w-full lg:w-px bg-vega-clight-500 dark:bg-vega-cdark-500" />
|
||||
<div className="px-6 py-2 lg:px-0" role="separator">
|
||||
<div className="w-full h-px lg:h-full lg:w-px bg-vega-clight-500 dark:bg-vega-cdark-500" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('GetStarted', () => {
|
||||
};
|
||||
const checkTicks = (elements: Element[]) => {
|
||||
elements.forEach((item, i) => {
|
||||
if (i + 1 < mockStep) {
|
||||
if (i + 1 < mockStep - 1) {
|
||||
expect(item.querySelector('[data-testid="icon-tick"]')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
@@ -57,19 +57,9 @@ describe('GetStarted', () => {
|
||||
'get'
|
||||
);
|
||||
navigatorGetter.mockReturnValue('Chrome');
|
||||
mockStep = 1;
|
||||
const { rerender, container } = renderComponent();
|
||||
expect(screen.queryByTestId('icon-tick')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('get-wallet-button')).toBeInTheDocument();
|
||||
|
||||
mockStep = 2;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
const { rerender, container } = renderComponent();
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
|
||||
|
||||
@@ -93,7 +83,9 @@ describe('GetStarted', () => {
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Ready to trade' })
|
||||
).toBeInTheDocument();
|
||||
|
||||
mockStep = 5;
|
||||
rerender(
|
||||
|
||||
@@ -7,11 +7,7 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
GetWalletButton,
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -30,7 +26,6 @@ interface Props {
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
@@ -47,15 +42,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
let onClickHandle = () => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
|
||||
return (
|
||||
<GetWalletButton
|
||||
className="justify-between"
|
||||
chromeExtensionUrl={CHROME_EXTENSION_URL}
|
||||
mozillaExtensionUrl={MOZILLA_EXTENSION_URL}
|
||||
/>
|
||||
);
|
||||
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
buttonText = t('Deposit');
|
||||
@@ -65,7 +52,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
dismiss();
|
||||
};
|
||||
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
buttonText = t('Dismiss');
|
||||
buttonText = t('Ready to trade');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Order });
|
||||
@@ -116,23 +103,18 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
<ul className="list-none">
|
||||
<Step
|
||||
step={1}
|
||||
text={t('Get a Vega wallet')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_WALLET_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
text={t('Connect')}
|
||||
complete={Boolean(
|
||||
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
|
||||
)}
|
||||
/>
|
||||
<Step
|
||||
step={3}
|
||||
step={2}
|
||||
text={t('Deposit funds')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={4}
|
||||
step={3}
|
||||
text={t('Open a position')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
|
||||
/>
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
|
||||
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
|
||||
@@ -23,7 +21,7 @@ let mockContext: Partial<VegaWalletContextShape> = { pubKey: 'test-pubkey' };
|
||||
describe('useGetOnboardingStep', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockData = [{ id: 'item-id' }];
|
||||
mockData = [];
|
||||
mockContext = { pubKey: 'test-pubkey' };
|
||||
globalThis.window.vega = {} as Vega;
|
||||
});
|
||||
@@ -42,13 +40,6 @@ describe('useGetOnboardingStep', () => {
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_UNKNOWN_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_WALLET_STEP', () => {
|
||||
// @ts-ignore test only purpose
|
||||
globalThis.window.vega = undefined;
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_WALLET_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_CONNECT_STEP', () => {
|
||||
mockContext = { pubKey: null };
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
@@ -56,15 +47,6 @@ describe('useGetOnboardingStep', () => {
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_DEPOSIT_STEP', async () => {
|
||||
(useDataProvider as jest.Mock).mockImplementation((args) => {
|
||||
if (
|
||||
args.dataProvider === depositsProvider ||
|
||||
args.dataProvider === aggregatedAccountsDataProvider
|
||||
) {
|
||||
return { data: [] };
|
||||
}
|
||||
return { data: mockData };
|
||||
});
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
await expect(result.current).toEqual(
|
||||
OnboardingStep.ONBOARDING_DEPOSIT_STEP
|
||||
@@ -72,6 +54,7 @@ describe('useGetOnboardingStep', () => {
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_ORDER_STEP', async () => {
|
||||
mockData = [{ id: 'item-id' }];
|
||||
(useDataProvider as jest.Mock).mockImplementation((args) => {
|
||||
if (
|
||||
args.dataProvider === ordersWithMarketProvider ||
|
||||
@@ -86,6 +69,7 @@ describe('useGetOnboardingStep', () => {
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_COMPLETE_STEP', async () => {
|
||||
mockData = [{ id: 'item-id' }];
|
||||
(useDataProvider as jest.Mock).mockImplementation(() => {
|
||||
return { data: mockData };
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isBrowserWalletInstalled, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
@@ -63,20 +63,21 @@ export const useGetOnboardingStep = () => {
|
||||
collateralData === null ||
|
||||
positionsData === null)
|
||||
);
|
||||
let step = OnboardingStep.ONBOARDING_UNKNOWN_STEP;
|
||||
if (isLoading) {
|
||||
return OnboardingStep.ONBOARDING_UNKNOWN_STEP;
|
||||
}
|
||||
if (!isBrowserWalletInstalled()) {
|
||||
return OnboardingStep.ONBOARDING_WALLET_STEP;
|
||||
return step;
|
||||
}
|
||||
if (!pubKey) {
|
||||
return OnboardingStep.ONBOARDING_CONNECT_STEP;
|
||||
step = OnboardingStep.ONBOARDING_CONNECT_STEP;
|
||||
}
|
||||
if (!deposits && !collaterals) {
|
||||
return OnboardingStep.ONBOARDING_DEPOSIT_STEP;
|
||||
if (pubKey) {
|
||||
step = OnboardingStep.ONBOARDING_DEPOSIT_STEP;
|
||||
}
|
||||
if (!orders && !positions) {
|
||||
return OnboardingStep.ONBOARDING_ORDER_STEP;
|
||||
if (pubKey && (deposits || collaterals)) {
|
||||
step = OnboardingStep.ONBOARDING_ORDER_STEP;
|
||||
}
|
||||
return OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
if (pubKey && (orders || positions)) {
|
||||
step = OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
}
|
||||
return step;
|
||||
};
|
||||
|
||||
@@ -6,14 +6,32 @@ import { Links, Routes } from '../../pages/client-router';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
const { data } = useMarketList();
|
||||
const markets = orderBy(
|
||||
data?.filter((m) => isMarketActive(m.state)) || [],
|
||||
[
|
||||
(m) => {
|
||||
if (!m.candles?.length) return 0;
|
||||
return Number(priceChangePercentage(m.candles.map((c) => c.close)));
|
||||
},
|
||||
],
|
||||
['desc']
|
||||
);
|
||||
const explore = () => {
|
||||
const marketId = markets?.[0].id ?? '';
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
};
|
||||
@@ -48,11 +66,11 @@ export const WelcomeDialogContent = () => {
|
||||
/>
|
||||
</ul>
|
||||
<TradingButton
|
||||
onClick={browseMarkets}
|
||||
onClick={explore}
|
||||
className="block w-full"
|
||||
data-testid="browse-markets-button"
|
||||
>
|
||||
{t('Browse the markets')}
|
||||
{t('Explore')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
<div className="sm:w-1/2 flex grow">
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
export const useMarketClickHandler = (replace = false) => {
|
||||
const navigate = useNavigate();
|
||||
const { marketId } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const isMarketPage = pathname.match(/^\/markets\/(.+)/);
|
||||
|
||||
return useCallback(
|
||||
(selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.MARKET](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else if (selectedId !== marketId || !isMarketPage) {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
},
|
||||
[navigate, marketId, replace, isMarketPage]
|
||||
);
|
||||
return (selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.MARKET](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const useMarketLiquidityClickHandler = () => {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
// Used for sort order and filter
|
||||
const MARKET_TEMPLATE = [
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketState.STATE_SUSPENDED,
|
||||
MarketState.STATE_PENDING,
|
||||
];
|
||||
|
||||
export const isMarketActive = (state: MarketState) => {
|
||||
return MARKET_TEMPLATE.includes(state);
|
||||
};
|
||||
@@ -82,7 +82,9 @@ html [data-theme='light'] {
|
||||
|
||||
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
|
||||
|
||||
--pennant-candlestick-inner-padding: 0.1;
|
||||
/* reduce space between candles */
|
||||
--pennant-candlestick-inner-padding: 0.175;
|
||||
--pennant-candlestick-stroke-width: 0.5;
|
||||
}
|
||||
|
||||
html [data-theme='light'] {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CandlestickChart } from 'pennant';
|
||||
import { VegaDataSource } from './data-source';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useMemo } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
@@ -22,8 +23,25 @@ export const CandlesChartContainer = ({
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { theme } = useThemeSwitcher();
|
||||
|
||||
const { interval, chartType, overlays, studies, merge } =
|
||||
useCandlesChartSettings();
|
||||
const {
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
} = useCandlesChartSettings();
|
||||
|
||||
const handlePaneChange = useMemo(
|
||||
() =>
|
||||
debounce((sizes: number[]) => {
|
||||
// first number is main pain, which is greedy so we don't store it
|
||||
setStudySizes(sizes.filter((_, i) => i !== 0));
|
||||
}, 300),
|
||||
[setStudySizes]
|
||||
);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return new VegaDataSource(client, marketId, pubKey);
|
||||
@@ -45,15 +63,16 @@ export const CandlesChartContainer = ({
|
||||
initialNumCandlesToDisplay: Math.floor(
|
||||
width * CANDLES_TO_WIDTH_FACTOR
|
||||
),
|
||||
studySize: 150, // default size
|
||||
studySizes,
|
||||
}}
|
||||
interval={interval}
|
||||
theme={theme}
|
||||
onOptionsChanged={(options) => {
|
||||
merge({
|
||||
overlays: options.overlays,
|
||||
studies: options.studies,
|
||||
});
|
||||
setStudies(options.studies);
|
||||
setOverlays(options.overlays);
|
||||
}}
|
||||
onPaneChanged={handlePaneChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,36 +5,45 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
type StudySizes = { [S in Study]?: number };
|
||||
|
||||
interface StoredSettings {
|
||||
interval: Interval;
|
||||
type: ChartType;
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: StudySizes;
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 100;
|
||||
const STUDY_ORDER: Study[] = [
|
||||
Study.FORCE_INDEX,
|
||||
Study.RELATIVE_STRENGTH_INDEX,
|
||||
Study.ELDAR_RAY,
|
||||
Study.MACD,
|
||||
Study.VOLUME,
|
||||
];
|
||||
|
||||
const DEFAULT_CHART_SETTINGS = {
|
||||
interval: Interval.I15M,
|
||||
type: ChartType.CANDLE,
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
studySizes: {},
|
||||
};
|
||||
|
||||
export const useCandlesChartSettingsStore = create<
|
||||
StoredSettings & {
|
||||
merge: (settings: Partial<StoredSettings>) => void;
|
||||
setType: (type: ChartType) => void;
|
||||
setInterval: (interval: Interval) => void;
|
||||
setOverlays: (overlays: Overlay[]) => void;
|
||||
setStudies: (studies: Study[]) => void;
|
||||
setOverlays: (overlays?: Overlay[]) => void;
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
immer((set) => ({
|
||||
...DEFAULT_CHART_SETTINGS,
|
||||
merge: (settings: Partial<StoredSettings>) =>
|
||||
set((state) => {
|
||||
Object.assign(state, settings);
|
||||
}),
|
||||
setType: (type) =>
|
||||
set((state) => {
|
||||
state.type = type;
|
||||
@@ -43,14 +52,35 @@ export const useCandlesChartSettingsStore = create<
|
||||
set((state) => {
|
||||
state.interval = interval;
|
||||
}),
|
||||
setOverlays: (overlays) =>
|
||||
setOverlays: (overlays) => {
|
||||
if (!overlays) return;
|
||||
|
||||
set((state) => {
|
||||
state.overlays = overlays;
|
||||
}),
|
||||
setStudies: (studies) =>
|
||||
});
|
||||
},
|
||||
setStudies: (studies) => {
|
||||
if (!studies) return;
|
||||
|
||||
// Make sure studies are always returned in the same order
|
||||
studies.sort((a, b) => {
|
||||
return STUDY_ORDER.indexOf(a) - STUDY_ORDER.indexOf(b);
|
||||
});
|
||||
|
||||
set((state) => {
|
||||
state.studies = studies;
|
||||
}),
|
||||
});
|
||||
},
|
||||
setStudySizes: (sizes) => {
|
||||
set((state) => {
|
||||
// for every study find the corresonding size and update
|
||||
// the size record for that study
|
||||
state.studies.forEach((s, i) => {
|
||||
const size = sizes[i];
|
||||
state.studySizes[s] = size;
|
||||
});
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: 'vega_candles_chart_store',
|
||||
@@ -85,15 +115,22 @@ export const useCandlesChartSettings = () => {
|
||||
[Study.VOLUME]
|
||||
);
|
||||
|
||||
// find the study size
|
||||
const studySizes = studies.map((s) => {
|
||||
const size = settings.studySizes[s] || STUDY_SIZE;
|
||||
return size;
|
||||
});
|
||||
|
||||
return {
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
setInterval: settings.setInterval,
|
||||
setType: settings.setType,
|
||||
setStudies: settings.setStudies,
|
||||
setOverlays: settings.setOverlays,
|
||||
merge: settings.merge,
|
||||
setStudySizes: settings.setStudySizes,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ export * from './lib/cells/centered-grid-cell';
|
||||
export * from './lib/cells/market-name-cell';
|
||||
export * from './lib/cells/order-type-cell';
|
||||
export * from './lib/cells/size';
|
||||
export * from './lib/cells/stacked-cell';
|
||||
|
||||
export * from './lib/filters/date-range-filter';
|
||||
export * from './lib/filters/set-filter';
|
||||
|
||||
@@ -59,7 +59,7 @@ export const MarketNameCell = ({
|
||||
(data as { market: Market })?.market?.tradableInstrument.instrument.product
|
||||
.__typename;
|
||||
|
||||
if (!value) return;
|
||||
if (!value) return null;
|
||||
const content = (
|
||||
<>
|
||||
<span data-testid="market-code" data-market-id={id}>
|
||||
@@ -72,7 +72,7 @@ export const MarketNameCell = ({
|
||||
<button
|
||||
onClick={handleOnClick}
|
||||
tabIndex={0}
|
||||
className="block text-left text-ellipsis overflow-hidden whitespace-nowrap w-full"
|
||||
className="block w-full overflow-hidden text-left text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
|
||||
@@ -11,8 +11,7 @@ import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
import * as Accordion from '@radix-ui/react-accordion';
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
|
||||
import {
|
||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||
@@ -24,7 +23,13 @@ import {
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import { KeyValue } from './key-value';
|
||||
import { TOOLTIP_TRIGGER_CLASS_NAME } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionChevron,
|
||||
AccordionPanel,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
@@ -246,31 +251,56 @@ export const DealTicketMarginDetails = ({
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Accordion.Root type="single" collapsible>
|
||||
<Accordion.Item value="margin">
|
||||
<KeyValue
|
||||
id="margin-required"
|
||||
label={
|
||||
<Accordion.Trigger className={TOOLTIP_TRIGGER_CLASS_NAME}>
|
||||
{t('Margin required')}
|
||||
</Accordion.Trigger>
|
||||
}
|
||||
value={formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals
|
||||
)}
|
||||
formattedValue={formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}
|
||||
labelDescription={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
<Accordion.Content>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<Accordion>
|
||||
<AccordionPanel
|
||||
itemId="margin"
|
||||
trigger={
|
||||
<AccordionPrimitive.Trigger
|
||||
data-testid="accordion-toggle"
|
||||
className={classNames(
|
||||
'w-full pt-2',
|
||||
'flex items-center gap-2 text-xs',
|
||||
'group'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
data-testid={`deal-ticket-fee-margin-required`}
|
||||
key={'value-dropdown'}
|
||||
className="flex items-center gap-2 justify-between w-full"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
|
||||
<span className="text-muted">{t('Margin required')}</span>
|
||||
</Tooltip>
|
||||
|
||||
<AccordionChevron size={10} />
|
||||
</div>
|
||||
<Tooltip
|
||||
description={
|
||||
formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals
|
||||
) ?? '-'
|
||||
}
|
||||
noUnderline
|
||||
>
|
||||
<div className="font-mono text-right">
|
||||
{formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}{' '}
|
||||
{assetSymbol || ''}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</AccordionPrimitive.Trigger>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<KeyValue
|
||||
label={t('Total margin available')}
|
||||
indent
|
||||
@@ -310,12 +340,12 @@ export const DealTicketMarginDetails = ({
|
||||
quantum
|
||||
)}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
{projectedMargin}
|
||||
<KeyValue
|
||||
label={t('Liquidation price estimate')}
|
||||
label={t('Liquidation')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
symbol={quoteName}
|
||||
@@ -329,6 +359,6 @@ export const DealTicketMarginDetails = ({
|
||||
onClose={onAccountBreakdownDialogClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -538,7 +538,7 @@ const NotionalAndFees = ({
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="mb-4 flex flex-col gap-2 w-full">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
|
||||
@@ -464,7 +464,7 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<div className="mb-4 flex flex-col gap-2 w-full">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
|
||||
@@ -25,11 +25,11 @@ export const KeyValue = ({
|
||||
}: KeyValuePros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
<button onClick={onClick} className="font-mono">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
<div className="font-mono">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
@@ -40,14 +40,14 @@ export const KeyValue = ({
|
||||
}`}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
'text-xs flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
<div className="text-muted">{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`} noUnderline>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||
[settlementAsset]
|
||||
);
|
||||
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
||||
'Estimated total margin that will cover open position, active orders and this order.'
|
||||
'Estimated total margin that will cover open positions, active orders and this order.'
|
||||
);
|
||||
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
|
||||
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||
@@ -60,7 +60,7 @@ export const EST_FEES_TOOLTIP_TEXT = t(
|
||||
);
|
||||
|
||||
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
|
||||
'This is a approximation to the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
||||
'This is an approximation (or a range) for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
||||
);
|
||||
|
||||
export const EST_SLIPPAGE = t(
|
||||
|
||||
+4
-2
@@ -7,12 +7,12 @@ export type DataSourceFilterFragment = { __typename?: 'Filter', key: { __typenam
|
||||
|
||||
export type DataSourceSpecFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
|
||||
|
||||
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
|
||||
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
|
||||
|
||||
export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
|
||||
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
|
||||
|
||||
export const DataSourceFilterFragmentDoc = gql`
|
||||
fragment DataSourceFilter on Filter {
|
||||
@@ -55,6 +55,8 @@ export const MarketFieldsFragmentDoc = gql`
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
parentMarketID
|
||||
successorMarketID
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
|
||||
@@ -2,7 +2,6 @@ export * from './fees-breakdown';
|
||||
export * from './last-24h-price-change';
|
||||
export * from './last-24h-volume';
|
||||
export * from './market-info';
|
||||
export * from './markets-container';
|
||||
export * from './oracle-banner';
|
||||
export * from './oracle-basic-profile';
|
||||
export * from './oracle-full-profile';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './markets-container';
|
||||
export * from './market-table-actions';
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
PriceFlashCell,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { OracleStatus } from './oracle-status';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||
|
||||
interface MarketNameCellProps {
|
||||
value?: string;
|
||||
data?: MarketMaybeWithData;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
const MarketName = (props: MarketNameCellProps) => (
|
||||
<>
|
||||
<MarketNameCell {...props} />
|
||||
{props.data ? (
|
||||
<OracleStatus
|
||||
dataSourceSpecForSettlementData={
|
||||
props.data.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData
|
||||
}
|
||||
dataSourceSpecForTradingTermination={
|
||||
props.data.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const defaultColDef = {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
type Props = TypedDataAgGrid<MarketMaybeWithData> & {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
};
|
||||
export const MarketListTable = ({
|
||||
onMarketClick,
|
||||
SuccessorMarketRenderer,
|
||||
...props
|
||||
}: Props) => {
|
||||
const columnDefs = useColumnDefs({ onMarketClick });
|
||||
const components = {
|
||||
PriceFlashCell,
|
||||
MarketName,
|
||||
...(SuccessorMarketRenderer ? { SuccessorMarketRenderer } : null),
|
||||
};
|
||||
return (
|
||||
<AgGrid
|
||||
getRowId={getRowId}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
components={components}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketListTable;
|
||||
@@ -1,179 +0,0 @@
|
||||
import { render, screen, act, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as DataProviders from '@vegaprotocol/data-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketsContainer } from './markets-container';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
const SuccessorMarketRenderer = ({ value }: { value: string }) => {
|
||||
return '-';
|
||||
};
|
||||
|
||||
const market = {
|
||||
id: 'id-1',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: { settlementAsset: { id: 'assetId-1' } },
|
||||
},
|
||||
},
|
||||
decimalPlaces: 1,
|
||||
positionDecimalPlaces: 1,
|
||||
state: 'STATE_ACTIVE',
|
||||
tradingMode: 'TRADING_MODE_OPENING_AUCTION',
|
||||
data: {
|
||||
bestBidPrice: 100,
|
||||
},
|
||||
} as unknown as MarketMaybeWithData;
|
||||
|
||||
describe('MarketsContainer', () => {
|
||||
const spyOnSelect = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
error: null,
|
||||
reload: jest.fn(),
|
||||
data: [market],
|
||||
};
|
||||
});
|
||||
});
|
||||
it('context menu should stay open', async () => {
|
||||
let rerenderRef: (ui: React.ReactElement) => void;
|
||||
await act(async () => {
|
||||
const { rerender } = render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
rerenderRef = rerender;
|
||||
});
|
||||
|
||||
// make sure ag grid is finished initializaing
|
||||
const rowContainer = await screen.findByRole('rowgroup', {
|
||||
name: (_name, element) =>
|
||||
element.classList.contains('ag-center-cols-container'),
|
||||
});
|
||||
expect(within(rowContainer).getAllByRole('row')).toHaveLength(1);
|
||||
expect(
|
||||
screen.getByRole('rowgroup', {
|
||||
name: (_name, element) =>
|
||||
element.classList.contains('ag-pinned-right-cols-container'),
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
|
||||
// open the dropdown
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: (_name, element) =>
|
||||
(element.parentNode as Element)?.getAttribute('id') ===
|
||||
'cell-market-actions-9',
|
||||
})
|
||||
);
|
||||
|
||||
await checkDropdown();
|
||||
|
||||
// reset the mock and rerender so the component
|
||||
// updates with new data
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
error: null,
|
||||
reload: jest.fn(),
|
||||
data: [{ ...market, state: 'STATE_PENDING' }],
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore we await the act above so rerenderRef is definitely defined
|
||||
rerenderRef(
|
||||
<MockedProvider mocks={[]}>
|
||||
<MarketsContainer onSelect={spyOnSelect} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// make sure dropdown is still open
|
||||
await checkDropdown();
|
||||
|
||||
async function checkDropdown() {
|
||||
const dropdownContent = await screen.findByTestId(
|
||||
'market-actions-content'
|
||||
);
|
||||
expect(dropdownContent).toBeInTheDocument();
|
||||
expect(
|
||||
within(dropdownContent).getByRole('menuitem', {
|
||||
name: 'Copy Market ID',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it('SuccessorMarketRenderer should be rendered', async () => {
|
||||
const successorMarketName = 'Successor Market Name';
|
||||
const spySuccessorMarketRenderer = jest
|
||||
.fn()
|
||||
.mockReturnValue(successorMarketName);
|
||||
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={spySuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(spySuccessorMarketRenderer).toHaveBeenCalled();
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarketID',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('presentation', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('id') === 'cell-successorMarketID-14',
|
||||
})
|
||||
).toHaveTextContent(successorMarketName);
|
||||
});
|
||||
|
||||
it('feature flag should hide successorMarketID column', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
|
||||
const spySuccessorMarketRenderer = jest.fn();
|
||||
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={spySuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(spySuccessorMarketRenderer).not.toHaveBeenCalled();
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('successorMarketID');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import type { MouseEvent } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketListTable } from './market-list-table';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketsWithDataProvider as dataProvider } from '../../markets-provider';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
|
||||
const POLLING_TIME = 2000;
|
||||
interface MarketsContainerProps {
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
}
|
||||
|
||||
export const MarketsContainer = ({
|
||||
onSelect,
|
||||
SuccessorMarketRenderer,
|
||||
}: MarketsContainerProps) => {
|
||||
const { data, error, reload } = useDataProvider({
|
||||
dataProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
reload();
|
||||
}, POLLING_TIME);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<MarketListTable
|
||||
rowData={data}
|
||||
onCellClicked={(cellEvent: CellClickedEvent) => {
|
||||
const { data, column, event } = cellEvent;
|
||||
// prevent navigating to the market page if any of the below cells are clicked
|
||||
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
|
||||
const colId = column.getColId();
|
||||
if (
|
||||
[
|
||||
'id',
|
||||
'tradableInstrument.instrument.code',
|
||||
'tradableInstrument.instrument.product.settlementAsset',
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
'market-actions',
|
||||
].includes(colId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
onSelect(
|
||||
(data as MarketMaybeWithData).id,
|
||||
(event as unknown as MouseEvent)?.metaKey ||
|
||||
(event as unknown as MouseEvent)?.ctrlKey
|
||||
);
|
||||
}}
|
||||
onMarketClick={onSelect}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
import { getMatchingOracleProvider, useOracleProofs } from '../../hooks';
|
||||
import type { Market } from '../../markets-provider';
|
||||
import { getVerifiedStatusIcon } from '../oracle-basic-profile';
|
||||
|
||||
export const OracleStatus = ({
|
||||
dataSourceSpecForSettlementData,
|
||||
dataSourceSpecForTradingTermination,
|
||||
}: Pick<
|
||||
Market['tradableInstrument']['instrument']['product'],
|
||||
'dataSourceSpecForSettlementData' | 'dataSourceSpecForTradingTermination'
|
||||
>) => {
|
||||
const { ORACLE_PROOFS_URL } = useEnvironment();
|
||||
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
|
||||
return useMemo(() => {
|
||||
if (providers) {
|
||||
const settlementDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForSettlementData.data,
|
||||
providers
|
||||
);
|
||||
const tradingTerminationDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForTradingTermination.data,
|
||||
providers
|
||||
);
|
||||
let maliciousOracleProvider = null;
|
||||
if (settlementDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = settlementDataProvider;
|
||||
} else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = tradingTerminationDataProvider;
|
||||
}
|
||||
if (!maliciousOracleProvider) return null;
|
||||
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
|
||||
return <Icon size={3} name={icon as IconName} className="ml-1" />;
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
providers,
|
||||
dataSourceSpecForSettlementData,
|
||||
dataSourceSpecForTradingTermination,
|
||||
]);
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
/*
|
||||
import { useMarketOverview } from '../../../../hooks/use-market-overview'
|
||||
import { colorByMarketMovement } from '../../../../lib/vega-colours'
|
||||
import { Sparkline } from '../../components/sparkline'
|
||||
import { VEGA_TABLE_CLASSES } from '../../components/vega-table'
|
||||
*/
|
||||
|
||||
export interface SummaryCellProps {
|
||||
value: string; // marketId
|
||||
}
|
||||
|
||||
export const SummaryCellView = ({ value }: SummaryCellProps) => {
|
||||
// const { sparkline, change, bullish } = useMarketOverview(value)
|
||||
// const color = colorByMarketMovement(bullish)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Sparkline data={sparkline} style={{ marginRight: 4 }} />*/}
|
||||
<span>{'change'}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
SummaryCellView.displayName = 'SummaryCellView';
|
||||
|
||||
export const SummaryCell = React.memo(SummaryCellView);
|
||||
SummaryCell.displayName = 'SummaryCell';
|
||||
@@ -1,205 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import compact from 'lodash/compact';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
|
||||
interface Props {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
|
||||
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return useMemo<ColDef[]>(
|
||||
() =>
|
||||
compact([
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketName',
|
||||
cellRendererParams: { onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
},
|
||||
{
|
||||
headerName: t('Trading mode'),
|
||||
field: 'tradingMode',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return '-';
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
return marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||
: Schema.MarketTradingModeMapping[marketTradingMode];
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketTradingModeMapping,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketStateMapping,
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
field: 'successorMarketID',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
headerName: t('Best bid'),
|
||||
field: 'data.bestBidPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.data?.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestBidPrice'
|
||||
>) =>
|
||||
data?.data?.bestBidPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Best offer'),
|
||||
field: 'data.bestOfferPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.data?.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Mark price'),
|
||||
field: 'data.markPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.markPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||
>) => {
|
||||
const value =
|
||||
data?.tradableInstrument.instrument.product.settlementAsset;
|
||||
return value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{value.symbol}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'market-actions',
|
||||
field: 'id',
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<MarketActionsDropdown
|
||||
marketId={data.id}
|
||||
assetId={
|
||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]),
|
||||
[onMarketClick, openAssetDetailsDialog]
|
||||
);
|
||||
};
|
||||
@@ -43,6 +43,7 @@ export const getVerifiedStatusIcon = (provider: Provider) => {
|
||||
const lastVerified = provider.oracle.last_verified
|
||||
? new Date(provider.oracle.last_verified)
|
||||
: new Date(provider.oracle.first_verified);
|
||||
|
||||
return {
|
||||
...getIconIntent(),
|
||||
message: t(
|
||||
@@ -112,13 +113,13 @@ export const OracleBasicProfile = ({
|
||||
<Icon size={3} name={icon as IconName} />
|
||||
</span>
|
||||
</span>
|
||||
<p className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2">
|
||||
<p className="mb-2 text-sm dark:text-vega-light-300 text-vega-dark-300">
|
||||
{message}
|
||||
</p>
|
||||
{oracleMarkets && (
|
||||
<p
|
||||
data-testid="signed-proofs"
|
||||
className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2"
|
||||
className="mb-2 text-sm dark:text-vega-light-300 text-vega-dark-300"
|
||||
>
|
||||
{t('Involved in %s %s', [
|
||||
oracleMarkets.length.toString(),
|
||||
@@ -130,9 +131,9 @@ export const OracleBasicProfile = ({
|
||||
<div className="flex flex-row gap-3">
|
||||
{links.map((link) => (
|
||||
<ExternalLink key={link.url} href={link.url} data-testid={link.url}>
|
||||
<span className="flex gap-1 items-center">
|
||||
<span className="flex items-center gap-1">
|
||||
<VegaIcon name={getLinkIcon(link.type)} />
|
||||
<span className="capitalize underline">{link.type}</span>
|
||||
<span className="underline capitalize">{link.type}</span>
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
|
||||
</span>
|
||||
</ExternalLink>
|
||||
|
||||
@@ -28,8 +28,6 @@ import {
|
||||
} from './market-utils';
|
||||
import { MarketsDocument } from './__generated__/markets';
|
||||
import type { Candle } from './market-candles-provider';
|
||||
import type { SuccessorMarketIdsQuery } from './__generated__/SuccessorMarket';
|
||||
import { SuccessorMarketIdsDocument } from './__generated__';
|
||||
|
||||
export type Market = MarketFieldsFragment;
|
||||
|
||||
@@ -240,34 +238,3 @@ export const useMarketList = () => {
|
||||
reload,
|
||||
};
|
||||
};
|
||||
|
||||
export type MarketSuccessors = {
|
||||
__typename?: 'Market';
|
||||
id: string;
|
||||
successorMarketID?: string | null;
|
||||
parentMarketID?: string | null;
|
||||
};
|
||||
const getMarketSuccessorData = (
|
||||
responseData: SuccessorMarketIdsQuery | null
|
||||
): MarketSuccessors[] | null =>
|
||||
responseData?.marketsConnection?.edges.map((edge) => edge.node) || null;
|
||||
|
||||
export const marketSuccessorProvider = makeDataProvider<
|
||||
SuccessorMarketIdsQuery,
|
||||
MarketSuccessors[],
|
||||
never,
|
||||
never
|
||||
>({
|
||||
query: SuccessorMarketIdsDocument,
|
||||
getData: getMarketSuccessorData,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
export const useSuccessorMarketIds = (marketId: string) => {
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: marketSuccessorProvider,
|
||||
variables: undefined,
|
||||
skip: !marketId,
|
||||
});
|
||||
return data?.find((item) => item.id === marketId) ?? null;
|
||||
};
|
||||
|
||||
@@ -36,6 +36,8 @@ fragment MarketFields on Market {
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
parentMarketID
|
||||
successorMarketID
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
|
||||
@@ -42,6 +42,8 @@ export const createMarketFragment = (
|
||||
close: null,
|
||||
open: null,
|
||||
},
|
||||
successorMarketID: null,
|
||||
parentMarketID: null,
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
MarketNameCell,
|
||||
ProgressBarCell,
|
||||
MarketProductPill,
|
||||
StackedCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
ButtonLink,
|
||||
@@ -40,7 +41,6 @@ import {
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionActionsDropdown } from './position-actions-dropdown';
|
||||
import { LiquidationPrice } from './liquidation-price';
|
||||
import { StackedCell } from './stacked-cell';
|
||||
|
||||
interface Props extends TypedDataAgGrid<Position> {
|
||||
onClose?: (data: Position) => void;
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
act,
|
||||
waitFor,
|
||||
getAllByRole,
|
||||
} from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import merge from 'lodash/merge';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
@@ -14,25 +8,13 @@ import { createProposalListFieldsFragment } from '../../lib/proposals-data-provi
|
||||
import type { ProposalsListQuery } from '../../lib';
|
||||
import { ProposalsListDocument } from '../../lib';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const successorMarketName = 'Successor Market Name';
|
||||
const spySuccessorMarketRenderer = jest
|
||||
.fn()
|
||||
.mockReturnValue(successorMarketName);
|
||||
const parentMarketName = 'Parent Market Name';
|
||||
const ParentMarketCell = () => <span>{parentMarketName}</span>;
|
||||
|
||||
describe('ProposalsList', () => {
|
||||
const rowContainerSelector = '.ag-center-cols-container';
|
||||
|
||||
const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
|
||||
const defaultProposalEdges = [
|
||||
{
|
||||
@@ -81,75 +63,55 @@ describe('ProposalsList', () => {
|
||||
|
||||
return mock;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be properly rendered', async () => {
|
||||
const mock = createProposalsMock();
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
const container = document.querySelector('.ag-center-cols-container');
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList cellRenderers={{ ParentMarketCell }} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('some of states should be filtered out', async () => {
|
||||
const proposalNode = createProposalListFieldsFragment({
|
||||
id: 'id-1',
|
||||
state: Types.ProposalState.STATE_ENACTED,
|
||||
expect(document.querySelector(rowContainerSelector)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const mock = createProposalsMock({
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
__typename: 'ProposalEdge',
|
||||
node: {
|
||||
...proposalNode,
|
||||
terms: {
|
||||
...proposalNode.terms,
|
||||
change: {
|
||||
...proposalNode.terms.change,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as PartialDeep<ProposalsListQuery>);
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
const container = document.querySelector('.ag-center-cols-container');
|
||||
await waitFor(() => {
|
||||
expect(container).toBeInTheDocument();
|
||||
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
||||
});
|
||||
const expectedHeaders = [
|
||||
'Market',
|
||||
'Settlement asset',
|
||||
'State',
|
||||
'Parent market',
|
||||
'Voting',
|
||||
'Closing date',
|
||||
'Enactment date',
|
||||
'', // actions col
|
||||
];
|
||||
|
||||
expect(spySuccessorMarketRenderer).toHaveBeenCalled();
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'parentMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
|
||||
).toEqual(expectedHeaders);
|
||||
|
||||
const container = within(
|
||||
document.querySelector(rowContainerSelector) as HTMLElement
|
||||
);
|
||||
expect(container.getAllByRole('row')).toHaveLength(
|
||||
// @ts-ignore data is mocked
|
||||
mock?.result?.data.proposalsConnection.edges.length
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getAllByRole('gridcell', {
|
||||
name: (name, element) =>
|
||||
element.getAttribute('col-id') === 'parentMarket',
|
||||
container.getAllByRole('gridcell', {
|
||||
name: (_, element) =>
|
||||
element.getAttribute('col-id') ===
|
||||
'terms.change.successorConfiguration.parentMarketId',
|
||||
})[0]
|
||||
).toHaveTextContent(successorMarketName);
|
||||
).toHaveTextContent(parentMarketName);
|
||||
});
|
||||
|
||||
it('empty response should causes no data message display', async () => {
|
||||
@@ -169,58 +131,11 @@ describe('ProposalsList', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
expect(await screen.findByText('No markets')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'parentMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('feature flag should hide parent marketcolumn', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
const mock: MockedResponse<ProposalsListQuery> = {
|
||||
request: {
|
||||
query: ProposalsListDocument,
|
||||
variables: {
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
proposalsConnection: {
|
||||
__typename: 'ProposalsConnection',
|
||||
edges: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) => element.getAttribute('col-id') === 'market',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('parentMarket');
|
||||
});
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList cellRenderers={{ ParentMarketCell }} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(await screen.findByText('No proposed markets')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import { MarketNameProposalCell, useColumnDefs } from './use-column-defs';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { useProposalsListQuery } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
||||
data.filter((proposal) =>
|
||||
@@ -16,13 +16,19 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
||||
].includes(proposal.state)
|
||||
);
|
||||
|
||||
const defaultColDef = {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
|
||||
interface ProposalListProps {
|
||||
SuccessorMarketRenderer: React.FC<{ value: string }>;
|
||||
cellRenderers: {
|
||||
[name: string]: FC<{ value: string; data: ProposalListFieldsFragment }>;
|
||||
};
|
||||
}
|
||||
|
||||
export const ProposalsList = ({
|
||||
SuccessorMarketRenderer,
|
||||
}: ProposalListProps) => {
|
||||
export const ProposalsList = ({ cellRenderers }: ProposalListProps) => {
|
||||
const { data } = useProposalsListQuery({
|
||||
variables: {
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
@@ -32,7 +38,7 @@ export const ProposalsList = ({
|
||||
const filteredData = getNewMarketProposals(
|
||||
removePaginationWrapper(data?.proposalsConnection?.edges)
|
||||
);
|
||||
const { columnDefs, defaultColDef } = useColumnDefs();
|
||||
const columnDefs = useColumnDefs();
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
@@ -40,8 +46,9 @@ export const ProposalsList = ({
|
||||
rowData={filteredData}
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={({ data }) => data.id}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
components={{ SuccessorMarketRenderer, MarketNameProposalCell }}
|
||||
overlayNoRowsTemplate={t('No proposed markets')}
|
||||
components={cellRenderers}
|
||||
rowHeight={45}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@ import {
|
||||
COL_DEFS,
|
||||
DateRangeFilter,
|
||||
SetFilter,
|
||||
StackedCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import compact from 'lodash/compact';
|
||||
import { useEnvironment, FLAGS } from '@vegaprotocol/environment';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -19,50 +19,15 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { ExternalLink, Pill } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ProposalProductTypeMapping,
|
||||
ProposalProductTypeShortName,
|
||||
ProductTypeMapping,
|
||||
ProductTypeShortName,
|
||||
ProposalStateMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { VoteProgress } from '../voting-progress';
|
||||
import { ProposalActionsDropdown } from '../proposal-actions-dropdown';
|
||||
|
||||
export const MarketNameProposalCell = ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.change.instrument.code'
|
||||
>) => {
|
||||
const { VEGA_TOKEN_URL } = useEnvironment();
|
||||
const { change } = data?.terms || {};
|
||||
if (change?.__typename === 'NewMarket' && VEGA_TOKEN_URL) {
|
||||
const type = change.instrument.futureProduct?.__typename;
|
||||
const content = (
|
||||
<>
|
||||
<span data-testid="market-code">{value as string}</span>
|
||||
{type && (
|
||||
<Pill
|
||||
size="xxs"
|
||||
className="uppercase ml-0.5"
|
||||
title={ProposalProductTypeMapping[type]}
|
||||
>
|
||||
{ProposalProductTypeShortName[type]}
|
||||
</Pill>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
if (data?.id) {
|
||||
const link = `${VEGA_TOKEN_URL}/proposals/${data.id}`;
|
||||
return <ExternalLink href={link}>{content}</ExternalLink>;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useColumnDefs = () => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
@@ -80,17 +45,36 @@ export const useColumnDefs = () => {
|
||||
headerName: t('Market'),
|
||||
field: 'terms.change.instrument.code',
|
||||
cellStyle: { lineHeight: '14px' },
|
||||
cellRenderer: 'MarketNameProposalCell',
|
||||
},
|
||||
{
|
||||
colId: 'description',
|
||||
headerName: t('Description'),
|
||||
field: 'terms.change.instrument.name',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: string;
|
||||
data: ProposalListFieldsFragment;
|
||||
}) => {
|
||||
if (!value || !data) return '-';
|
||||
|
||||
// TODO: update when we switch to ProductConfiguration
|
||||
const productType = 'Future';
|
||||
return (
|
||||
<StackedCell
|
||||
primary={value}
|
||||
secondary={
|
||||
<span
|
||||
title={ProductTypeMapping[productType]}
|
||||
className="uppercase"
|
||||
>
|
||||
{ProductTypeShortName[productType]}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'asset',
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'terms.change.instrument.futureProduct.settlementAsset.name',
|
||||
field: 'terms.change.instrument.futureProduct.settlementAsset.symbol',
|
||||
},
|
||||
{
|
||||
colId: 'state',
|
||||
@@ -105,12 +89,10 @@ export const useColumnDefs = () => {
|
||||
set: ProposalStateMapping,
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
{
|
||||
headerName: t('Parent market'),
|
||||
field: 'id',
|
||||
colId: 'parentMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
cellRendererParams: { parent: true },
|
||||
field: 'terms.change.successorConfiguration.parentMarketId',
|
||||
cellRenderer: 'ParentMarketCell',
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
@@ -169,25 +151,12 @@ export const useColumnDefs = () => {
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (!data?.id) return null;
|
||||
|
||||
return <ProposalActionsDropdown id={data.id} />;
|
||||
},
|
||||
},
|
||||
]);
|
||||
}, [requiredMajorityPercentage]);
|
||||
|
||||
const defaultColDef: ColDef = useMemo(() => {
|
||||
return {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
}, []);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
columnDefs,
|
||||
defaultColDef,
|
||||
}),
|
||||
[columnDefs, defaultColDef]
|
||||
);
|
||||
return columnDefs;
|
||||
};
|
||||
|
||||
@@ -114,6 +114,9 @@ fragment NewMarketFields on NewMarket {
|
||||
lpPriceRange
|
||||
# linearSlippageFactor
|
||||
# quadraticSlippageFactor
|
||||
successorConfiguration {
|
||||
parentMarketId
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateMarketFields on UpdateMarket {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -109,6 +109,58 @@ export const marketUpdateProposal: ProposalListFieldsFragment = {
|
||||
export const createProposalListFieldsFragment = (
|
||||
override?: PartialDeep<ProposalListFieldsFragment>
|
||||
): ProposalListFieldsFragment => {
|
||||
const newMarket = {
|
||||
decimalPlaces: 1,
|
||||
lpPriceRange: '',
|
||||
riskParameters: {
|
||||
__typename: 'SimpleRiskModel',
|
||||
params: {
|
||||
__typename: 'SimpleRiskModelParams',
|
||||
factorLong: 0,
|
||||
factorShort: 1,
|
||||
},
|
||||
},
|
||||
metadata: undefined,
|
||||
successorConfiguration: {
|
||||
__typename: 'SuccessorConfiguration',
|
||||
parentMarketId: 'xyz',
|
||||
},
|
||||
instrument: {
|
||||
code: 'ETHUSD',
|
||||
name: 'ETHUSD',
|
||||
futureProduct: {
|
||||
settlementAsset: {
|
||||
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
|
||||
name: 'tDAI TEST',
|
||||
symbol: 'tDAI',
|
||||
decimals: 1,
|
||||
quantum: '1',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
quoteName: '',
|
||||
dataSourceSpecBinding: {
|
||||
__typename: 'DataSourceSpecToFutureBinding',
|
||||
settlementDataProperty: '',
|
||||
tradingTerminationProperty: '',
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
} as const;
|
||||
const defaultProposal: ProposalListFieldsFragment = {
|
||||
id: 'e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829',
|
||||
reference: 'injected_at_runtime',
|
||||
@@ -147,54 +199,7 @@ export const createProposalListFieldsFragment = (
|
||||
terms: {
|
||||
closingDatetime: '2022-11-15T12:44:34Z',
|
||||
enactmentDatetime: '2022-11-15T12:44:54Z',
|
||||
change: {
|
||||
decimalPlaces: 1,
|
||||
lpPriceRange: '',
|
||||
riskParameters: {
|
||||
__typename: 'SimpleRiskModel',
|
||||
params: {
|
||||
__typename: 'SimpleRiskModelParams',
|
||||
factorLong: 0,
|
||||
factorShort: 1,
|
||||
},
|
||||
},
|
||||
metadata: [],
|
||||
instrument: {
|
||||
code: 'ETHUSD',
|
||||
name: 'ETHUSD',
|
||||
futureProduct: {
|
||||
settlementAsset: {
|
||||
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
|
||||
name: 'tDAI TEST',
|
||||
symbol: 'tDAI',
|
||||
decimals: 1,
|
||||
quantum: '1',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
quoteName: '',
|
||||
dataSourceSpecBinding: {
|
||||
__typename: 'DataSourceSpecToFutureBinding',
|
||||
settlementDataProperty: '',
|
||||
tradingTerminationProperty: '',
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
change: newMarket,
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
import classNames from 'classnames';
|
||||
import type { VegaIconProps } from '../icon';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
|
||||
export interface AccordionItemProps {
|
||||
@@ -24,6 +25,28 @@ export const Accordion = ({ panels, children }: AccordionProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const AccordionPanel = ({
|
||||
trigger,
|
||||
children,
|
||||
itemId,
|
||||
}: {
|
||||
trigger: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
itemId: string;
|
||||
}) => {
|
||||
return (
|
||||
<AccordionPrimitive.Item value={itemId}>
|
||||
<AccordionPrimitive.Header>{trigger}</AccordionPrimitive.Header>
|
||||
<AccordionPrimitive.Content
|
||||
className="py-3 text-sm"
|
||||
data-testid="accordion-content"
|
||||
>
|
||||
{children}
|
||||
</AccordionPrimitive.Content>
|
||||
</AccordionPrimitive.Item>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccordionItem = ({
|
||||
title,
|
||||
content,
|
||||
@@ -31,7 +54,7 @@ export const AccordionItem = ({
|
||||
}: AccordionPanelProps) => {
|
||||
const triggerClassNames = classNames(
|
||||
'w-full py-2',
|
||||
'flex items-center justify-between border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
|
||||
'flex items-center justify-between gap-2 border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
|
||||
'group'
|
||||
);
|
||||
return (
|
||||
@@ -41,7 +64,9 @@ export const AccordionItem = ({
|
||||
data-testid="accordion-toggle"
|
||||
className={triggerClassNames}
|
||||
>
|
||||
<span data-testid="accordion-title">{title}</span>
|
||||
<span data-testid="accordion-title" className="flex-1 text-left">
|
||||
{title}
|
||||
</span>
|
||||
<AccordionChevron aria-hidden />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
@@ -55,15 +80,17 @@ export const AccordionItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const AccordionChevron = () => {
|
||||
export const AccordionChevron = ({
|
||||
size = 16,
|
||||
}: Pick<VegaIconProps, 'size'>) => {
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
'transform transition ease-in-out duration-300',
|
||||
'flex transform transition ease-in-out duration-300',
|
||||
'group-data-[state=open]:rotate-180'
|
||||
)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden />
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden size={size} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Portal,
|
||||
} from '@radix-ui/react-tooltip';
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const tooltipContentClasses =
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs text-black dark:text-white break-word';
|
||||
@@ -18,10 +19,14 @@ export interface TooltipProps {
|
||||
align?: 'start' | 'center' | 'end';
|
||||
side?: 'top' | 'right' | 'bottom' | 'left';
|
||||
sideOffset?: number;
|
||||
noUnderline?: boolean;
|
||||
}
|
||||
|
||||
export const TOOLTIP_TRIGGER_CLASS_NAME =
|
||||
'underline underline-offset-2 decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed';
|
||||
export const TOOLTIP_TRIGGER_CLASS_NAME = (noUnderline?: boolean) =>
|
||||
classNames(
|
||||
{ 'underline underline-offset-2': !noUnderline },
|
||||
'decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed'
|
||||
);
|
||||
|
||||
// Conditionally rendered tooltip if description content is provided.
|
||||
export const Tooltip = ({
|
||||
@@ -31,11 +36,12 @@ export const Tooltip = ({
|
||||
sideOffset,
|
||||
align = 'start',
|
||||
side = 'bottom',
|
||||
noUnderline,
|
||||
}: TooltipProps) =>
|
||||
description ? (
|
||||
<Provider delayDuration={200} skipDelayDuration={100}>
|
||||
<Root open={open}>
|
||||
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME}>
|
||||
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(noUnderline)}>
|
||||
{children}
|
||||
</Trigger>
|
||||
{description && (
|
||||
|
||||
@@ -39,17 +39,17 @@ const getClassName = (
|
||||
},
|
||||
// colours
|
||||
{
|
||||
'bg-vega-yellow hover:bg-vega-yellow-550 dark:bg-vega-yellow dark:hover:bg-vega-yellow-450':
|
||||
'bg-vega-yellow enabled:hover:bg-vega-yellow-550 dark:bg-vega-yellow dark:enabled:hover:bg-vega-yellow-450':
|
||||
intent === Intent.Primary,
|
||||
'bg-vega-clight-500 hover:bg-vega-clight-400 dark:bg-vega-cdark-500 dark:hover:bg-vega-cdark-400':
|
||||
'bg-vega-clight-500 enabled:hover:bg-vega-clight-400 dark:bg-vega-cdark-500 dark:enabled:hover:bg-vega-cdark-400':
|
||||
intent === Intent.None,
|
||||
'bg-vega-blue-350 hover:bg-vega-blue-400 dark:bg-vega-blue-650 dark:hover:bg-vega-blue-600':
|
||||
'bg-vega-blue-350 enabled:hover:bg-vega-blue-400 dark:bg-vega-blue-650 dark:enabled:hover:bg-vega-blue-600':
|
||||
intent === Intent.Info,
|
||||
'bg-vega-orange-350 hover:bg-vega-orange-400 dark:bg-vega-orange-650 dark:hover:bg-vega-orange-600':
|
||||
'bg-vega-orange-350 enabled:hover:bg-vega-orange-400 dark:bg-vega-orange-650 dark:enabled:hover:bg-vega-orange-600':
|
||||
intent === Intent.Warning,
|
||||
'bg-vega-red-350 hover:bg-vega-red-400 dark:bg-vega-red-650 dark:hover:bg-vega-red-600':
|
||||
'bg-vega-red-350 enabled:hover:bg-vega-red-400 dark:bg-vega-red-650 dark:enabled:hover:bg-vega-red-600':
|
||||
intent === Intent.Danger,
|
||||
'bg-vega-green-350 hover:bg-vega-green-400 dark:bg-vega-green-650 dark:hover:bg-vega-green-600':
|
||||
'bg-vega-green-350 enabled:hover:bg-vega-green-400 dark:bg-vega-green-650 dark:enabled:hover:bg-vega-green-600':
|
||||
intent === Intent.Success,
|
||||
'text-vega-clight-50 dark:text-vega-cdark-50': intent !== Intent.Primary,
|
||||
'text-vega-clight-900 dark:text-vega-cdark-900':
|
||||
@@ -73,7 +73,7 @@ const Content = ({
|
||||
children,
|
||||
}: Pick<TradingButtonProps, 'icon' | 'subLabel' | 'children'>) => (
|
||||
<>
|
||||
<span data-label className="font-alpha leading-none" key="children">
|
||||
<span data-label className="leading-none font-alpha" key="children">
|
||||
{children}
|
||||
</span>
|
||||
{icon}
|
||||
|
||||
@@ -241,51 +241,49 @@ const ConnectorList = ({
|
||||
'Connect securely, deposit funds and approve or reject transactions with the Vega wallet'
|
||||
)}
|
||||
</p>
|
||||
<div data-testid="connectors-list" className="flex flex-col mt-4 gap-4">
|
||||
<div>
|
||||
{isBrowserWalletInstalled() ? (
|
||||
<ConnectionOptionWithDescription
|
||||
type="injected"
|
||||
text={extendedText}
|
||||
onClick={() => onSelect('injected')}
|
||||
title={
|
||||
<>
|
||||
<span>{t('Vega Wallet')}</span>
|
||||
{' '}
|
||||
<span className="text-xs">{t('full featured')}</span>
|
||||
</>
|
||||
}
|
||||
description={t(
|
||||
`Connect Vega Wallet extension
|
||||
<div data-testid="connectors-list" className="flex flex-col mt-4 gap-2">
|
||||
{isBrowserWalletInstalled() ? (
|
||||
<ConnectionOptionWithDescription
|
||||
type="injected"
|
||||
text={extendedText}
|
||||
onClick={() => onSelect('injected')}
|
||||
title={
|
||||
<>
|
||||
<span>{t('Vega Wallet')}</span>
|
||||
{' '}
|
||||
<span className="text-xs">{t('full featured')}</span>
|
||||
</>
|
||||
}
|
||||
description={t(
|
||||
`Connect Vega Wallet extension
|
||||
for %s to access all features including key
|
||||
management and detailed transaction views from your
|
||||
browser.`,
|
||||
[browserName]
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
<h1 className="mb-1 text-lg">
|
||||
<span>{t('Vega Wallet')}</span>
|
||||
{' '}
|
||||
<span className="text-xs"> {t('full featured')}</span>
|
||||
</h1>
|
||||
<p className="mb-2 text-sm">
|
||||
{t(
|
||||
`Install Vega Wallet extension
|
||||
for %s to access all features including key
|
||||
management and detailed transaction views from your
|
||||
browser.`,
|
||||
[browserName]
|
||||
)}
|
||||
</p>
|
||||
<GetWalletButton
|
||||
chromeExtensionUrl={links.chromeExtensionUrl}
|
||||
mozillaExtensionUrl={links.mozillaExtensionUrl}
|
||||
/>
|
||||
) : (
|
||||
<div className="py-2">
|
||||
<h1 className="px-2 text-lg">
|
||||
<span>{t('Vega Wallet')}</span>
|
||||
{' '}
|
||||
<span className="text-xs"> {t('full featured')}</span>
|
||||
</h1>
|
||||
<p className="p-2 text-sm">
|
||||
{t(
|
||||
`Install Vega Wallet extension
|
||||
for %s to access all features including key
|
||||
management and detailed transaction views from your
|
||||
browser.`,
|
||||
[browserName]
|
||||
)}
|
||||
</p>
|
||||
<GetWalletButton
|
||||
chromeExtensionUrl={links.chromeExtensionUrl}
|
||||
mozillaExtensionUrl={links.mozillaExtensionUrl}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{connectors['snap'] !== undefined ? (
|
||||
<div>
|
||||
{snapStatus === SnapStatus.INSTALLED ? (
|
||||
@@ -335,7 +333,7 @@ const ConnectorList = ({
|
||||
}}
|
||||
/>
|
||||
{snapStatus === SnapStatus.NOT_SUPPORTED ? (
|
||||
<p className="pt-1 text-xs leading-tight text-default">
|
||||
<p className="pt-1 text-xs leading-tight text-muted">
|
||||
{t('No MetaMask version that supports snaps detected.')}{' '}
|
||||
{t('Learn more about')}{' '}
|
||||
<ExternalLink href="https://metamask.io/snaps/">
|
||||
@@ -348,7 +346,7 @@ const ConnectorList = ({
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<h1 className="my-1 text-md">{t('Advanced / Other options...')}</h1>
|
||||
<h1 className="mb-1 text-md">{t('Advanced / Other options...')}</h1>
|
||||
<ConnectionOption
|
||||
type="view"
|
||||
text={t('View as party')}
|
||||
@@ -520,7 +518,7 @@ const ConnectionOptionWithDescription = ({
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-md">{title}</h1>
|
||||
<p className="pb-2 text-sm text-gray-60 text-muted">{description}</p>
|
||||
<p className="mb-2 text-sm text-gray-60 text-muted">{description}</p>
|
||||
<ConnectionOption
|
||||
disabled={disabled}
|
||||
type={type}
|
||||
@@ -579,10 +577,11 @@ const CustomUrlInput = ({
|
||||
<div className="flex justify-between mb-1.5">
|
||||
<p className="text-sm text-secondary">{t('Custom wallet location')}</p>
|
||||
<button
|
||||
className="text-sm underline"
|
||||
className="text-sm text-muted"
|
||||
onClick={() => setUrlInputExpanded(false)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.ARROW_LEFT} /> {t('Go back')}
|
||||
<VegaIcon name={VegaIconNames.ARROW_LEFT} />{' '}
|
||||
<span className="underline underline-offset-4">{t('Go back')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<TradingFormGroup
|
||||
@@ -612,18 +611,20 @@ const CustomUrlInput = ({
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
/>
|
||||
{isDesktopWalletRunning !== null && (
|
||||
<p className="pt-1 mb-2 text-sm leading-tight">
|
||||
<div className="pt-1 mb-2 text-sm">
|
||||
{isDesktopWalletRunning ? (
|
||||
<button
|
||||
className="underline text-default"
|
||||
className="text-muted"
|
||||
onClick={() => setUrlInputExpanded(true)}
|
||||
disabled={Boolean(pubKey)}
|
||||
>
|
||||
{t('Enter a custom wallet location')}{' '}
|
||||
<span className="underline underline-offset-4">
|
||||
{t('Enter a custom wallet location')}
|
||||
</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<p className="leading-tight text-muted">
|
||||
<span className="text-xs">
|
||||
{t(
|
||||
'No running Desktop App/CLI detected. Open your app now to connect or enter a'
|
||||
@@ -636,9 +637,9 @@ const CustomUrlInput = ({
|
||||
>
|
||||
{t('custom wallet location')}
|
||||
</button>
|
||||
</>
|
||||
</p>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -88,10 +88,13 @@ export function ViewConnectorForm({
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={reset}
|
||||
className="p-2 text-sm underline"
|
||||
className="p-2 text-sm"
|
||||
data-testid="back-button"
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.ARROW_LEFT} /> {t('Go back')}
|
||||
<VegaIcon name={VegaIconNames.ARROW_LEFT} />{' '}
|
||||
<span className="underline underline-offset-4">
|
||||
{t('Go back')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "13.3.0",
|
||||
"pennant": "1.11.1",
|
||||
"pennant": "1.12.0",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
@@ -20253,10 +20253,10 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
pennant@1.11.1:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.11.1.tgz#f47bceaade01db215eeba666cd755840d16fe9ed"
|
||||
integrity sha512-U26OxjxETWLJAvCFj20oH0y5gzyBfJ5oD4O3dYg1aWnH4giUYDPWuUDFnzlMJ+yXolyYECccXm6S1BkoVcVzDg==
|
||||
pennant@1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.12.0.tgz#e12707d5f1aac554d81bad060637e608335e0b50"
|
||||
integrity sha512-xosg5erRf+Ke9iORdqyv+SOGcD3uJX1dgf990q1DvHuzz36w2txCZsfnvcXhRO++HYVKS9sU/YLPRLJgolFGtA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@d3fc/d3fc-technical-indicator" "^8.0.1"
|
||||
|
||||
Reference in New Issue
Block a user