Compare commits

...
Author SHA1 Message Date
Dexter 8e2c51f7c0 ci: bump 2023-03-20 12:24:00 +00:00
Dexter 67afff2baf feat: allow any icons for buttons 2023-03-20 12:23:39 +00:00
dexturr ca63a671c8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 12:09:43 +00:00
Maciek 4165f3905f chore(static): update domains in network conf files (#3219) 2023-03-20 09:58:05 +00:00
dexturr a359ccb755 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 06:07:41 +00:00
dexturr e584737005 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 00:17:40 +00:00
dexturr b12c5ed39f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 18:08:07 +00:00
dexturr d3dbd64cf1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 12:07:36 +00:00
dexturr 2df62bca61 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 06:07:48 +00:00
dexturr fd2a434b46 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 00:17:10 +00:00
dexturr 4e7a78f988 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 18:08:23 +00:00
dexturr 327619652a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 12:07:24 +00:00
dexturr d2339205cc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 06:06:50 +00:00
dexturr 63df4cf465 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 00:13:54 +00:00
dexturr 5e34178291 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 18:06:54 +00:00
dexturr 0c44664aa5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 12:07:53 +00:00
Bartłomiej Głownia 7ef1d75529 chore(trading): remove red background from trades table (#3214) 2023-03-17 09:52:40 +00:00
Maciek 84795b2d6a chore(trading): split bottom panel into two parts (#3205) 2023-03-17 09:50:43 +00:00
dexturr 300019f108 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 06:09:09 +00:00
dexturr 2d0aebd3fe chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 00:14:43 +00:00
dexturr 9e86d2aff5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 18:09:41 +00:00
daro-maj 0f7e76ae01 test(trading): increase coverage for 0004-ewal (#3210) 2023-03-16 13:29:40 +01:00
dexturr 56732d1503 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 12:08:59 +00:00
Joe Tsang 5c19263653 chore(governance): include slippage factor to proposals (#3204) 2023-03-16 10:41:18 +00:00
dexturr 0d911b3029 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 06:08:43 +00:00
Matthew RussellandEdd 9f059e7862 chore(explorer,governance): update be endpoints (#3183)
Co-authored-by: Edd <edd@vega.xyz>
2023-03-15 20:12:04 -07:00
dexturr 92ea93dadb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 00:16:50 +00:00
daro-maj 6165aefad7 test(trading): fixed settlement asset displayed test (#3209) 2023-03-15 18:38:57 +00:00
dexturr c95369d914 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-15 18:08:52 +00:00
Maciek 5dbc5d7997 chore(trading): make loading masks in tabs consistent along the app (#3155) 2023-03-15 16:08:48 +01:00
dexturr 47b3d5b55f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-15 12:07:50 +00:00
Maciek da7c0b84f7 chore(trading): display latency in block height progressing as warn or error (#3169) 2023-03-15 12:49:31 +01:00
Art 67e602ebb1 feat(explorer): try again for tx details (#3195) 2023-03-15 08:24:42 +00:00
Art f407110e95 chore(environment): etherscan link (#3193) 2023-03-15 08:24:20 +00:00
Art 66a85cce2a chore(explorer): market info shared panels (#3192) 2023-03-15 08:21:12 +00:00
Art 93f8450ecb fix(utils): missing flags (#3194) 2023-03-15 08:20:41 +00:00
dexturr 0447a6cf5d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-15 06:09:39 +00:00
dexturr 498be838c7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-15 00:17:07 +00:00
66 changed files with 3184 additions and 1907 deletions
+3 -3
View File
@@ -2,11 +2,11 @@
NX_VEGA_ENV=VALIDATOR_TESTNET NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/ NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.xyz NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
@@ -10,7 +10,7 @@ export const Footer = () => {
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false); const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo( const showFullFeedbackLabel = useMemo(
() => ['lg', 'xl'].includes(screenSize), () => ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
[screenSize] [screenSize]
); );
@@ -1,143 +1,52 @@
import {
addDecimalsFormatNumber,
formatNumberPercentage,
getMarketExpiryDateFormatted,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/market-info'; import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info'; import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
import { import {
MarketStateMapping, InstrumentInfoPanel,
MarketTradingModeMapping, KeyDetailsInfoPanel,
} from '@vegaprotocol/types'; LiquidityPriceRangeInfoPanel,
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets'; MetadataInfoPanel,
import { Splash } from '@vegaprotocol/ui-toolkit'; OracleInfoPanel,
import BigNumber from 'bignumber.js'; RiskFactorsInfoPanel,
import { useMemo } from 'react'; RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel,
} from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => { export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
);
const { data: asset } = useAssetDataProvider(assetId ?? '');
if (!market) return null; if (!market) return null;
const keyDetails = {
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
tradingMode: market.tradingMode,
state: MarketStateMapping[market.state],
};
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const panels = [ const panels = [
{ {
title: t('Key details'), title: t('Key details'),
content: ( content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
<MarketInfoTable
noBorder={false}
data={{
name: market.tradableInstrument.instrument.name,
marketID: market.id,
tradingMode:
keyDetails.tradingMode &&
MarketTradingModeMapping[keyDetails.tradingMode],
marketDecimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
settlementAssetDecimalPlaces: assetDecimals,
}}
/>
),
}, },
{ {
title: t('Instrument'), title: t('Instrument'),
content: ( content: <InstrumentInfoPanel noBorder={false} market={market} />,
<MarketInfoTable
noBorder={false}
data={{
marketName: market.tradableInstrument.instrument.name,
code: market.tradableInstrument.instrument.code,
productType:
market.tradableInstrument.instrument.product.__typename,
...market.tradableInstrument.instrument.product,
}}
/>
),
}, },
{ {
title: t('Settlement asset'), title: t('Settlement asset'),
content: asset ? ( content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
<AssetDetailsTable
asset={asset}
inline={true}
noBorder={false}
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/>
) : (
<Splash>{t('No data')}</Splash>
),
}, },
{ {
title: t('Metadata'), title: t('Metadata'),
content: ( content: <MetadataInfoPanel noBorder={false} market={market} />,
<MarketInfoTable
noBorder={false}
data={{
expiryDate: getMarketExpiryDateFormatted(
market.tradableInstrument.instrument.metadata.tags
),
...market.tradableInstrument.instrument.metadata.tags
?.map((tag) => {
const [key, value] = tag.split(':');
return { [key]: value };
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
}}
/>
),
}, },
{ {
title: t('Risk model'), title: t('Risk model'),
content: ( content: <RiskModelInfoPanel noBorder={false} market={market} />,
<MarketInfoTable
noBorder={false}
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
/>
),
}, },
{ {
title: t('Risk parameters'), title: t('Risk parameters'),
content: ( content: <RiskParametersInfoPanel noBorder={false} market={market} />,
<MarketInfoTable
noBorder={false}
data={market.tradableInstrument.riskModel.params}
unformatted={true}
omits={[]}
/>
),
}, },
{ {
title: t('Risk factors'), title: t('Risk factors'),
content: ( content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
<MarketInfoTable
noBorder={false}
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
/>
),
}, },
...(market.priceMonitoringSettings?.parameters?.triggers || []).map( ...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({ (trigger, i) => ({
@@ -158,7 +67,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<MarketInfoTable <MarketInfoTable
noBorder={false} noBorder={false}
data={{ referencePrice: trigger.referencePrice }} data={{ referencePrice: trigger.referencePrice }}
decimalPlaces={assetDecimals} decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/> />
</> </>
), ),
@@ -166,64 +78,26 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
{ {
title: t('Liquidity monitoring parameters'), title: t('Liquidity monitoring parameters'),
content: ( content: (
<MarketInfoTable <LiquidityMonitoringParametersInfoPanel
noBorder={false} noBorder={false}
data={{ market={market}
triggeringRatio:
market.liquidityMonitoringParameters.triggeringRatio,
...market.liquidityMonitoringParameters.targetStakeParameters,
}}
/> />
), ),
}, },
{
title: t('Liquidity'),
content: <LiquidityInfoPanel market={market} noBorder={false} />,
},
{ {
title: t('Liquidity price range'), title: t('Liquidity price range'),
content: ( content: (
<> <LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
<p className="text-xs mb-4">
{`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`}
</p>
<p className="text-xs mb-4">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<MarketInfoTable
noBorder={false}
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
></MarketInfoTable>
</>
), ),
}, },
{ {
title: t('Oracle'), title: t('Oracle'),
content: ( content: (
<MarketInfoTable <OracleInfoPanel noBorder={false} market={market}>
noBorder={false}
data={
market.tradableInstrument.instrument.product.dataSourceSpecBinding
}
>
<Link <Link
className="text-xs hover:underline" className="text-xs hover:underline"
to={`/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`} to={`/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
@@ -236,7 +110,7 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
> >
{t('View termination oracle specification')} {t('View termination oracle specification')}
</Link> </Link>
</MarketInfoTable> </OracleInfoPanel>
), ),
}, },
]; ];
@@ -244,7 +118,7 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
return ( return (
<> <>
{panels.map((p) => ( {panels.map((p) => (
<div className="mb-3"> <div key={p.title} className="mb-3">
<h2 className="font-alpha calt text-xl">{p.title}</h2> <h2 className="font-alpha calt text-xl">{p.title}</h2>
{p.content} {p.content}
</div> </div>
@@ -1,4 +1,5 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Button } from '@vegaprotocol/ui-toolkit';
import { StatusMessage } from '../status-message'; import { StatusMessage } from '../status-message';
interface RenderFetchedProps { interface RenderFetchedProps {
@@ -7,6 +8,7 @@ interface RenderFetchedProps {
loading: boolean | undefined; loading: boolean | undefined;
className?: string; className?: string;
errorMessage?: string; errorMessage?: string;
refetch?: () => void;
} }
export const RenderFetched = ({ export const RenderFetched = ({
@@ -15,6 +17,7 @@ export const RenderFetched = ({
children, children,
className, className,
errorMessage = t('Error retrieving data'), errorMessage = t('Error retrieving data'),
refetch,
}: RenderFetchedProps) => { }: RenderFetchedProps) => {
if (loading) { if (loading) {
return ( return (
@@ -23,7 +26,20 @@ export const RenderFetched = ({
} }
if (error) { if (error) {
return <StatusMessage className={className}>{errorMessage}</StatusMessage>; return (
<>
<StatusMessage className={className}>{errorMessage}</StatusMessage>
{refetch && (
<Button
onClick={() => {
refetch();
}}
>
{t('Try again')}
</Button>
)}
</>
);
} }
return children; return children;
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog'; import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { marketInfoProvider } from '@vegaprotocol/market-info'; import { marketInfoWithDataProvider } from '@vegaprotocol/market-info';
import { PageTitle } from '../../components/page-helpers/page-title'; import { PageTitle } from '../../components/page-helpers/page-title';
export const MarketPage = () => { export const MarketPage = () => {
@@ -17,7 +17,7 @@ export const MarketPage = () => {
const { marketId } = useParams<{ marketId: string }>(); const { marketId } = useParams<{ marketId: string }>();
const { data, loading, error } = useDataProvider({ const { data, loading, error } = useDataProvider({
dataProvider: marketInfoProvider, dataProvider: marketInfoWithDataProvider,
skipUpdates: true, skipUpdates: true,
variables: { variables: {
marketId: marketId || '', marketId: marketId || '',
@@ -22,6 +22,7 @@ const Tx = () => {
const { const {
state: { data, loading, error }, state: { data, loading, error },
refetch,
} = useFetch<BlockExplorerTransaction>( } = useFetch<BlockExplorerTransaction>(
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}` `${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}`
); );
@@ -56,6 +57,7 @@ const Tx = () => {
error={error} error={error}
loading={loading} loading={loading}
errorMessage={errorMessage} errorMessage={errorMessage}
refetch={refetch}
> >
<TxDetails <TxDetails
className="mb-28" className="mb-28"
@@ -21,7 +21,7 @@ import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { PageTitle } from '../../components/page-helpers/page-title'; import { PageTitle } from '../../components/page-helpers/page-title';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { import {
ContractAddressLink, EtherscanLink,
DApp, DApp,
TOKEN_VALIDATOR, TOKEN_VALIDATOR,
useLinks, useLinks,
@@ -224,7 +224,7 @@ export const ValidatorsPage = () => {
<KeyValueTableRow> <KeyValueTableRow>
<div>{t('Ethereum address')}</div> <div>{t('Ethereum address')}</div>
<div className="break-all text-xs"> <div className="break-all text-xs">
<ContractAddressLink address={v.ethereumAddress} />{' '} <EtherscanLink address={v.ethereumAddress} />{' '}
<CopyWithTooltip text={v.ethereumAddress}> <CopyWithTooltip text={v.ethereumAddress}>
<button title={t('Copy address to clipboard')}> <button title={t('Copy address to clipboard')}>
<Icon size={3} name="duplicate" /> <Icon size={3} name="duplicate" />
@@ -2,6 +2,8 @@
"changes": { "changes": {
"decimalPlaces": "5", "decimalPlaces": "5",
"positionDecimalPlaces": "5", "positionDecimalPlaces": "5",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0",
"lpPriceRange": "10", "lpPriceRange": "10",
"instrument": { "instrument": {
"name": "Token test market", "name": "Token test market",
@@ -1,5 +1,7 @@
{ {
"lpPriceRange": "10", "lpPriceRange": "10",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0",
"instrument": { "instrument": {
"code": "TEST.24h", "code": "TEST.24h",
"future": { "future": {
@@ -110,10 +110,9 @@ Cypress.Commands.add('vega_wallet_teardown', function () {
} }
}); });
cy.get(vegaWalletContainer).within(() => { cy.get(vegaWalletContainer).within(() => {
cy.get('[data-testid="associated-amount"]', { timeout: 30000 }).should( cy.get('[data-testid="vega-wallet-balance-unstaked"]', {
'contain.text', timeout: 30000,
'0.00' }).should('contain.text', '0.00');
);
}); });
}); });
+1 -1
View File
@@ -2,7 +2,7 @@
NX_VEGA_ENV=VALIDATOR_TESTNET NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/ NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}' NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
@@ -2,7 +2,7 @@ import type { Dispatch, SetStateAction } from 'react';
import { forwardRef, useMemo, useRef, useState } from 'react'; import { forwardRef, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context'; import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber'; import { BigNumber } from '../../../../lib/bignumber';
@@ -87,7 +87,12 @@ const TopThirdCellRenderer = (
<div className="mb-4"> <div className="mb-4">
<Button <Button
data-testid="show-all-validators" data-testid="show-all-validators"
rightIcon="arrow-right" rightIcon={
<Icon
name="arrow-right"
className="fill-current mr-2 align-text-top"
/>
}
className="inline-flex items-center" className="inline-flex items-center"
> >
{t('Reveal top validators')} {t('Reveal top validators')}
+10 -22
View File
@@ -1,7 +1,6 @@
import { import {
KeyValueTable, KeyValueTable,
KeyValueTableRow, KeyValueTableRow,
Link,
RoundedWrapper, RoundedWrapper,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
@@ -11,7 +10,7 @@ import { useParams } from 'react-router';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import { formatNumber } from '@vegaprotocol/utils'; import { formatNumber } from '@vegaprotocol/utils';
import { useEnvironment } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import { TrancheItem } from '../redemption/tranche-item'; import { TrancheItem } from '../redemption/tranche-item';
import Routes from '../routes'; import Routes from '../routes';
import { TrancheLabel } from './tranche-label'; import { TrancheLabel } from './tranche-label';
@@ -19,7 +18,6 @@ import { useTranches } from '../../lib/tranches/tranches-store';
export const Tranche = () => { export const Tranche = () => {
const tranches = useTranches((state) => state.tranches); const tranches = useTranches((state) => state.tranches);
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { trancheId } = useParams<{ trancheId: string; address: string }>(); const { trancheId } = useParams<{ trancheId: string; address: string }>();
const { chainId } = useWeb3React(); const { chainId } = useWeb3React();
@@ -59,25 +57,15 @@ export const Tranche = () => {
</KeyValueTableRow> </KeyValueTableRow>
{tranche.users.map((user) => ( {tranche.users.map((user) => (
<KeyValueTableRow key={user}> <KeyValueTableRow key={user}>
{ <EtherscanLink address={user} data-testid="link" />
<Link <RouterLink
title={t('View on Etherscan (opens in a new tab)')} className="underline"
href={`${ETHERSCAN_URL}/address/${user}`} title={t('View vesting information')}
target="_blank" to={`${Routes.REDEEM}/${user}`}
> data-testid="redeem-link"
{user} >
</Link> {t('View vesting information')}
} </RouterLink>
{
<RouterLink
className="underline"
title={t('View vesting information')}
to={`${Routes.REDEEM}/${user}`}
data-testid="redeem-link"
>
{t('View vesting information')}
</RouterLink>
}
</KeyValueTableRow> </KeyValueTableRow>
))} ))}
</KeyValueTable> </KeyValueTable>
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"hosts": ["https://api.n04.d.vega.xyz/graphql"] "hosts": ["https://api-n04.d.vega.rocks/graphql"]
} }
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"hosts": ["https://api.n00.mainnet-mirror.vega.xyz/graphql"] "hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"]
} }
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"hosts": ["https://api.n01.sandbox.vega.xyz/graphql"] "hosts": ["https://api-n01.sandbox.vega.rocks/graphql"]
} }
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"hosts": ["https://api.n00.stagnet1.vega.xyz/graphql"] "hosts": ["https://api-n00.stagnet1.vega.rocks/graphql"]
} }
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"hosts": ["https://api.stagnet3.vega.xyz/graphql"] "hosts": ["https://api-stagnet3.vega.rocks/graphql"]
} }
+7 -7
View File
@@ -1,11 +1,11 @@
{ {
"hosts": [ "hosts": [
"https://api.n06.testnet.vega.xyz/graphql", "https://api-n06.testnet.vega.rocks/graphql",
"https://api.n07.testnet.vega.xyz/graphql", "https://api-n07.testnet.vega.rocks/graphql",
"https://api.n08.testnet.vega.xyz/graphql", "https://api-n08.testnet.vega.rocks/graphql",
"https://api.n09.testnet.vega.xyz/graphql", "https://api-n09.testnet.vega.rocks/graphql",
"https://api.n10.testnet.vega.xyz/graphql", "https://api-n10.testnet.vega.rocks/graphql",
"https://api.n11.testnet.vega.xyz/graphql", "https://api-n11.testnet.vega.rocks/graphql",
"https://api.n12.testnet.vega.xyz/graphql" "https://api-n12.testnet.vega.rocks/graphql"
] ]
} }
@@ -7,6 +7,10 @@ const externalLink = 'external-link';
const accordionContent = 'accordion-content'; const accordionContent = 'accordion-content';
describe('market info is displayed', { tags: '@smoke' }, () => { describe('market info is displayed', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
});
before(() => { before(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockSubscription(); cy.mockSubscription();
@@ -216,6 +216,60 @@ describe('Market trading page', () => {
}); });
}); });
}); });
describe('market bottom panel', { tags: '@smoke' }, () => {
it('on xxl screen should be splitted out into two tables', () => {
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.viewport(1801, 1000);
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('Fills').click();
cy.getByTestId('Collateral').click();
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'active'
);
});
});
}); });
describe('market states not accepting orders', { tags: '@smoke' }, function () { describe('market states not accepting orders', { tags: '@smoke' }, function () {
@@ -0,0 +1,58 @@
import { connectEthereumWallet } from '../support/ethereum-wallet';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
// 0004-EWAL-001
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('should see an option to cancel the attempted connection', () => {
// 0004-EWAL-003
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-WalletConnect').click();
cy.get('#walletconnect-qrcode-text').should('exist');
cy.get('#walletconnect-qrcode-close').click();
});
it('able to disconnect eth wallet', () => {
// 0004-EWAL-004
// 0004-EWAL-005
// 0004-EWAL-006
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
@@ -1,7 +1,8 @@
import { mockConnectWallet } from '@vegaprotocol/cypress'; import {
import { connectEthereumWallet } from '../support/ethereum-wallet'; mockConnectWallet,
mockConnectWalletWithUserError,
} from '@vegaprotocol/cypress';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
const connectVegaBtn = 'connect-vega-wallet'; const connectVegaBtn = 'connect-vega-wallet';
const manageVegaBtn = 'manage-vega-wallet'; const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form'; const form = 'rest-connector-form';
@@ -127,6 +128,21 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
cy.getByTestId(manageVegaBtn).should('exist'); cy.getByTestId(manageVegaBtn).should('exist');
}); });
it('can not connect', () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
mockConnectWalletWithUserError();
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.getByTestId('dialog-content')
.should('contain.text', 'User error')
.and('contain.text', 'the user rejected the wallet connection');
});
it('can change selected public key and disconnect', () => { it('can change selected public key and disconnect', () => {
// 0002-WCON-022 // 0002-WCON-022
// 0002-WCON-023 // 0002-WCON-023
@@ -164,39 +180,3 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
); );
}); });
}); });
describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('able to disconnect eth wallet', () => {
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
+6 -39
View File
@@ -1,9 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import debounce from 'lodash/debounce';
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
useDataProvider, useDataProvider,
useScreenDimensions,
useThrottledDataProvider, useThrottledDataProvider,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
@@ -61,7 +61,8 @@ export const MarketPage = () => {
const { marketId } = useParams(); const { marketId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const { w } = useWindowSize(); const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update); const update = useGlobalStore((store) => store.update);
const lastMarketId = useGlobalStore((store) => store.marketId); const lastMarketId = useGlobalStore((store) => store.marketId);
@@ -87,7 +88,7 @@ export const MarketPage = () => {
}, [update, lastMarketId, data?.id]); }, [update, lastMarketId, data?.id]);
const tradeView = useMemo(() => { const tradeView = useMemo(() => {
if (w > 960) { if (largeScreen) {
return ( return (
<TradeGrid <TradeGrid
market={data} market={data}
@@ -105,7 +106,7 @@ export const MarketPage = () => {
onClickCollateral={() => navigate('/portfolio')} onClickCollateral={() => navigate('/portfolio')}
/> />
); );
}, [w, data, onSelect, navigate]); }, [largeScreen, data, onSelect, navigate]);
if (!data && marketId) { if (!data && marketId) {
return ( return (
<Splash> <Splash>
@@ -140,37 +141,3 @@ export const MarketPage = () => {
</AsyncRenderer> </AsyncRenderer>
); );
}; };
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState(() => {
if (typeof window !== 'undefined') {
return {
w: window.innerWidth,
h: window.innerHeight,
};
}
// Something sensible for server rendered page
return {
w: 1200,
h: 900,
};
});
useEffect(() => {
const handleResize = debounce(({ target }) => {
setWindowSize({
w: target.innerWidth,
h: target.innerHeight,
});
}, 300);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowSize;
};
+195 -119
View File
@@ -8,7 +8,7 @@ import { TradesContainer } from '@vegaprotocol/trades';
import { LayoutPriority } from 'allotment'; import { LayoutPriority } from 'allotment';
import classNames from 'classnames'; import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { memo, useState } from 'react'; import { memo, useCallback, useState } from 'react';
import type { ReactNode, ComponentProps } from 'react'; import type { ReactNode, ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
@@ -29,6 +29,7 @@ import { LiquidityContainer } from '../liquidity/liquidity';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router'; import { Links, Routes } from '../../pages/client-router';
import type { PinnedAsset } from '@vegaprotocol/accounts'; import type { PinnedAsset } from '@vegaprotocol/accounts';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
type MarketDependantView = type MarketDependantView =
| typeof CandlesChartContainer | typeof CandlesChartContainer
@@ -69,129 +70,204 @@ interface TradeGridProps {
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
} }
const MainGrid = ({ interface BottomPanelProps {
marketId,
onSelect,
pinnedAsset,
}: {
marketId: string; marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
}) => { }
const navigate = useNavigate();
const onMarketClick = (marketId: string) => { const MarketBottomPanel = memo(
navigate(Links[Routes.MARKET](marketId), { ({ marketId, pinnedAsset }: BottomPanelProps) => {
replace: true, const { screenSize } = useScreenDimensions();
}); const navigate = useNavigate();
}; const onMarketClick = useCallback(
return ( (marketId: string) => {
<ResizableGrid vertical> navigate(Links[Routes.MARKET](marketId), {
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}> replace: true,
<ResizableGrid proportionalLayout={false} minSize={200}> });
<ResizableGridPanel },
priority={LayoutPriority.High} [navigate]
minSize={200} );
preferredSize="50%"
> return 'xxxl' === screenSize ? (
<TradeGridChild> <ResizableGrid proportionalLayout minSize={200}>
<Tabs> <ResizableGridPanel
<Tab id="chart" name={t('Chart')}> priority={LayoutPriority.Low}
<TradingViews.Candles marketId={marketId} /> preferredSize="50%"
</Tab> minSize={50}
<Tab id="depth" name={t('Depth')}> >
<TradingViews.Depth marketId={marketId} /> <TradeGridChild>
</Tab> <Tabs>
<Tab id="liquidity" name={t('Liquidity')}> <Tab id="orders" name={t('Orders')}>
<TradingViews.Liquidity marketId={marketId} /> <VegaWalletContainer>
</Tab> <TradingViews.Orders
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={330}
minSize={300}
>
<TradeGridChild>
<Tabs>
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket
marketId={marketId} marketId={marketId}
onClickCollateral={() => navigate('/portfolio')} onMarketClick={onMarketClick}
/> />
</Tab> </VegaWalletContainer>
<Tab id="info" name={t('Info')}> </Tab>
<TradingViews.Info <Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId} marketId={marketId}
onSelect={(id: string) => { onMarketClick={onMarketClick}
onSelect?.(id);
}}
/> />
</Tab> </VegaWalletContainer>
</Tabs> </Tab>
</TradeGridChild> </Tabs>
</ResizableGridPanel> </TradeGridChild>
<ResizableGridPanel </ResizableGridPanel>
priority={LayoutPriority.Low} <ResizableGridPanel
preferredSize={430} priority={LayoutPriority.Low}
minSize={200} preferredSize="50%"
> minSize={50}
<TradeGridChild> >
<Tabs> <TradeGridChild>
<Tab id="orderbook" name={t('Orderbook')}> <Tabs>
<TradingViews.Orderbook marketId={marketId} /> <Tab id="positions" name={t('Positions')}>
</Tab> <VegaWalletContainer>
<Tab id="trades" name={t('Trades')}> <TradingViews.Positions
<TradingViews.Trades marketId={marketId} /> onMarketClick={onMarketClick}
</Tab> noBottomPlaceholder
</Tabs> />
</TradeGridChild> </VegaWalletContainer>
</ResizableGridPanel> </Tab>
</ResizableGrid> <Tab id="accounts" name={t('Collateral')}>
</ResizableGridPanel> <VegaWalletContainer>
<ResizableGridPanel <TradingViews.Collateral
priority={LayoutPriority.Low} pinnedAsset={pinnedAsset}
preferredSize="25%" noBottomPlaceholder
minSize={50} hideButtons
> />
<TradeGridChild> </VegaWalletContainer>
<Tabs> </Tab>
<Tab id="positions" name={t('Positions')}> </Tabs>
<VegaWalletContainer> </TradeGridChild>
<TradingViews.Positions onMarketClick={onMarketClick} /> </ResizableGridPanel>
</VegaWalletContainer> </ResizableGrid>
</Tab> ) : (
<Tab id="orders" name={t('Orders')}> <TradeGridChild>
<VegaWalletContainer> <Tabs>
<TradingViews.Orders <Tab id="positions" name={t('Positions')}>
marketId={marketId} <VegaWalletContainer>
onMarketClick={onMarketClick} <TradingViews.Positions onMarketClick={onMarketClick} />
/> </VegaWalletContainer>
</VegaWalletContainer> </Tab>
</Tab> <Tab id="orders" name={t('Orders')}>
<Tab id="fills" name={t('Fills')}> <VegaWalletContainer>
<VegaWalletContainer> <TradingViews.Orders
<TradingViews.Fills marketId={marketId}
marketId={marketId} onMarketClick={onMarketClick}
onMarketClick={onMarketClick} />
/> </VegaWalletContainer>
</VegaWalletContainer> </Tab>
</Tab> <Tab id="fills" name={t('Fills')}>
<Tab id="accounts" name={t('Collateral')}> <VegaWalletContainer>
<VegaWalletContainer> <TradingViews.Fills
<TradingViews.Collateral marketId={marketId}
pinnedAsset={pinnedAsset} onMarketClick={onMarketClick}
hideButtons />
/> </VegaWalletContainer>
</VegaWalletContainer> </Tab>
</Tab> <Tab id="accounts" name={t('Collateral')}>
</Tabs> <VegaWalletContainer>
</TradeGridChild> <TradingViews.Collateral pinnedAsset={pinnedAsset} hideButtons />
</ResizableGridPanel> </VegaWalletContainer>
</ResizableGrid> </Tab>
); </Tabs>
}; </TradeGridChild>
const MainGridWrapped = memo(MainGrid); );
}
);
MarketBottomPanel.displayName = 'MarketBottomPanel';
const MainGrid = memo(
({
marketId,
onSelect,
pinnedAsset,
}: {
marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset;
}) => {
const navigate = useNavigate();
return (
<ResizableGrid vertical>
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
<ResizableGrid proportionalLayout={false} minSize={200}>
<ResizableGridPanel
priority={LayoutPriority.High}
minSize={200}
preferredSize="50%"
>
<TradeGridChild>
<Tabs>
<Tab id="chart" name={t('Chart')}>
<TradingViews.Candles marketId={marketId} />
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={marketId} />
</Tab>
<Tab id="liquidity" name={t('Liquidity')}>
<TradingViews.Liquidity marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={330}
minSize={300}
>
<TradeGridChild>
<Tabs>
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket
marketId={marketId}
onClickCollateral={() => navigate('/portfolio')}
/>
</Tab>
<Tab id="info" name={t('Info')}>
<TradingViews.Info
marketId={marketId}
onSelect={(id: string) => {
onSelect?.(id);
}}
/>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={430}
minSize={200}
>
<TradeGridChild>
<Tabs>
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={marketId} />
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="25%"
minSize={50}
>
<MarketBottomPanel marketId={marketId} pinnedAsset={pinnedAsset} />
</ResizableGridPanel>
</ResizableGrid>
);
}
);
MainGrid.displayName = 'MainGrid';
export const TradeGrid = ({ export const TradeGrid = ({
market, market,
@@ -201,7 +277,7 @@ export const TradeGrid = ({
return ( return (
<div className="h-full grid grid-rows-[min-content_1fr]"> <div className="h-full grid grid-rows-[min-content_1fr]">
<TradeMarketHeader market={market} onSelect={onSelect} /> <TradeMarketHeader market={market} onSelect={onSelect} />
<MainGridWrapped <MainGrid
marketId={market?.id || ''} marketId={market?.id || ''}
onSelect={onSelect} onSelect={onSelect}
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
@@ -25,7 +25,8 @@ export const DepositsContainer = () => {
<div className="h-full relative"> <div className="h-full relative">
<DepositsTable <DepositsTable
rowData={data || []} rowData={data || []}
noRowsOverlayComponent={() => null} suppressLoadingOverlay
suppressNoRowsOverlay
ref={gridRef} ref={gridRef}
{...bottomPlaceholderProps} {...bottomPlaceholderProps}
/> />
@@ -24,7 +24,8 @@ export const WithdrawalsContainer = () => {
<WithdrawalsTable <WithdrawalsTable
data-testid="withdrawals-history" data-testid="withdrawals-history"
rowData={data} rowData={data}
noRowsOverlayComponent={() => null} suppressLoadingOverlay
suppressNoRowsOverlay
/> />
<div className="pointer-events-none absolute inset-0"> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer <AsyncRenderer
@@ -12,9 +12,11 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = ({ export const AccountsContainer = ({
pinnedAsset, pinnedAsset,
hideButtons, hideButtons,
noBottomPlaceholder,
}: { }: {
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
hideButtons?: boolean; hideButtons?: boolean;
noBottomPlaceholder?: boolean;
}) => { }) => {
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -46,6 +48,7 @@ export const AccountsContainer = ({
onClickDeposit={openDepositDialog} onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
noBottomPlaceholder={noBottomPlaceholder}
/> />
{!isReadOnly && !hideButtons && ( {!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded"> <div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
+33 -15
View File
@@ -1,20 +1,30 @@
import { render, screen } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { NodeHealth, NodeUrl, HealthIndicator } from './footer'; import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
import { MockedProvider } from '@apollo/client/testing';
import { Intent } from '@vegaprotocol/ui-toolkit';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
useEnvironment: jest
.fn()
.mockImplementation(() => ({ VEGA_URL: 'https://vega-url.wtf' })),
}));
const mockSetNodeSwitcher = jest.fn();
jest.mock('../../stores', () => ({
...jest.requireActual('../../stores'),
useGlobalStore: () => mockSetNodeSwitcher,
}));
describe('NodeHealth', () => { describe('NodeHealth', () => {
it('controls the node switcher dialog', async () => { it('controls the node switcher dialog', async () => {
const mockOnClick = jest.fn(); render(<NodeHealth />, { wrapper: MockedProvider });
render( await waitFor(() => {
<NodeHealth expect(screen.getByRole('button')).toBeInTheDocument();
onClick={mockOnClick} });
url={'https://api.n99.somenetwork.vega.xyz'}
blockHeight={100}
blockDiff={0}
/>
);
await userEvent.click(screen.getByRole('button')); await userEvent.click(screen.getByRole('button'));
expect(mockOnClick).toHaveBeenCalled(); expect(mockSetNodeSwitcher).toHaveBeenCalled();
}); });
}); });
@@ -31,14 +41,22 @@ describe('NodeUrl', () => {
describe('HealthIndicator', () => { describe('HealthIndicator', () => {
const cases = [ const cases = [
{ diff: 0, classname: 'bg-vega-green-550', text: 'Operational' }, {
{ diff: 5, classname: 'bg-warning', text: '5 Blocks behind' }, intent: Intent.Success,
{ diff: null, classname: 'bg-danger', text: 'Non operational' }, text: 'Operational',
classname: 'bg-vega-green-550',
},
{
intent: Intent.Warning,
text: '5 Blocks behind',
classname: 'bg-warning',
},
{ intent: Intent.Danger, text: 'Non operational', classname: 'bg-danger' },
]; ];
it.each(cases)( it.each(cases)(
'renders correct text and indicator color for $diff block difference', 'renders correct text and indicator color for $diff block difference',
(elem) => { (elem) => {
render(<HealthIndicator blockDiff={elem.diff} />); render(<HealthIndicator text={elem.text} intent={elem.intent} />);
expect(screen.getByTestId('indicator')).toHaveClass(elem.classname); expect(screen.getByTestId('indicator')).toHaveClass(elem.classname);
expect(screen.getByText(elem.text)).toBeInTheDocument(); expect(screen.getByText(elem.text)).toBeInTheDocument();
} }
+21 -56
View File
@@ -1,60 +1,45 @@
import { useCallback } from 'react';
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment'; import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Indicator, Intent } from '@vegaprotocol/ui-toolkit'; import type { Intent } from '@vegaprotocol/ui-toolkit';
import { Indicator } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames'; import classNames from 'classnames';
import type { ButtonHTMLAttributes, ReactNode } from 'react'; import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useGlobalStore } from '../../stores'; import { useGlobalStore } from '../../stores';
export const Footer = () => { export const Footer = () => {
const { VEGA_URL } = useEnvironment();
const setNodeSwitcher = useGlobalStore(
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
);
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
return ( return (
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black"> <footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black">
{/* Pull left to align with top nav, due to button padding */} {/* Pull left to align with top nav, due to button padding */}
<div className="-ml-2"> <div className="-ml-2">
{VEGA_URL && ( <NodeHealth />
<NodeHealth
url={VEGA_URL}
blockHeight={datanodeBlockHeight}
blockDiff={blockDiff}
onClick={() => setNodeSwitcher(true)}
/>
)}
</div> </div>
</footer> </footer>
); );
}; };
interface NodeHealthProps {
url: string;
blockHeight: number | undefined;
blockDiff: number | null;
onClick: () => void;
}
export const NodeHealth = ({ export const NodeHealth = () => {
url, const { VEGA_URL } = useEnvironment();
blockHeight, const setNodeSwitcher = useGlobalStore(
blockDiff, (store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
onClick, );
}: NodeHealthProps) => { const { datanodeBlockHeight, text, intent } = useNodeHealth();
return ( const onClick = useCallback(() => {
setNodeSwitcher(true);
}, [setNodeSwitcher]);
return VEGA_URL ? (
<FooterButton onClick={onClick} data-testid="node-health"> <FooterButton onClick={onClick} data-testid="node-health">
<FooterButtonPart> <FooterButtonPart>
<HealthIndicator blockDiff={blockDiff} /> <HealthIndicator text={text} intent={intent} />
</FooterButtonPart> </FooterButtonPart>
<FooterButtonPart> <FooterButtonPart>
<NodeUrl url={url} /> <NodeUrl url={VEGA_URL} />
</FooterButtonPart> </FooterButtonPart>
<FooterButtonPart> <FooterButtonPart>
<span title={t('Block height')}>{blockHeight}</span> <span title={t('Block height')}>{datanodeBlockHeight}</span>
</FooterButtonPart> </FooterButtonPart>
</FooterButton> </FooterButton>
); ) : null;
}; };
interface NodeUrlProps { interface NodeUrlProps {
@@ -69,31 +54,11 @@ export const NodeUrl = ({ url }: NodeUrlProps) => {
}; };
interface HealthIndicatorProps { interface HealthIndicatorProps {
blockDiff: number | null; text: string;
intent: Intent;
} }
// How many blocks behind the most advanced block that is export const HealthIndicator = ({ text, intent }: HealthIndicatorProps) => {
// deemed acceptable for "Good" status
const BLOCK_THRESHOLD = 3;
export const HealthIndicator = ({ blockDiff }: HealthIndicatorProps) => {
const online = useNavigatorOnline();
let intent = Intent.Success;
let text = 'Operational';
if (!online) {
text = t('Offline');
intent = Intent.Danger;
} else if (blockDiff === null) {
// Block height query failed and null was returned
text = t('Non operational');
intent = Intent.Danger;
} else if (blockDiff >= BLOCK_THRESHOLD) {
text = t(`${blockDiff} Blocks behind`);
intent = Intent.Warning;
}
return ( return (
<span title={t('Node health')}> <span title={t('Node health')}>
<Indicator variant={intent} /> <Indicator variant={intent} />
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useAssetsDataProvider } from '@vegaprotocol/assets'; import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { ETHERSCAN_TX, useEtherscanLink } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import { formatNumber, toBigNum } from '@vegaprotocol/utils'; import { formatNumber, toBigNum } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit'; import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
@@ -8,7 +8,7 @@ import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit'; import { Panel } from '@vegaprotocol/ui-toolkit';
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit'; import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit'; import { useToasts } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Intent, ProgressBar } from '@vegaprotocol/ui-toolkit'; import { Intent, ProgressBar } from '@vegaprotocol/ui-toolkit';
import { useCallback } from 'react'; import { useCallback } from 'react';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import type { EthStoredTxState } from '@vegaprotocol/web3'; import type { EthStoredTxState } from '@vegaprotocol/web3';
@@ -103,7 +103,7 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
<> <>
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading> <ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
<p>{t('Please wait for your transaction to be confirmed.')}</p> <p>{t('Please wait for your transaction to be confirmed.')}</p>
<EtherscanLink tx={tx} /> {tx.txHash && <EtherscanLink tx={tx.txHash} />}
<EthTransactionDetails tx={tx} /> <EthTransactionDetails tx={tx} />
</> </>
); );
@@ -126,26 +126,12 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
); );
}; };
const EtherscanLink = ({ tx }: EthTxToastContentProps) => {
const etherscanLink = useEtherscanLink();
return tx.txHash ? (
<p className="break-all">
<ExternalLink
href={etherscanLink(ETHERSCAN_TX.replace(':hash', tx.txHash))}
rel="noreferrer"
>
{t('View on Etherscan')}
</ExternalLink>
</p>
) : null;
};
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => { const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
return ( return (
<> <>
<ToastHeading>{t('Transaction confirmed')}</ToastHeading> <ToastHeading>{t('Transaction confirmed')}</ToastHeading>
<p>{t('Your transaction has been confirmed.')}</p> <p>{t('Your transaction has been confirmed.')}</p>
<EtherscanLink tx={tx} /> {tx.txHash && <EtherscanLink tx={tx.txHash} />}
<EthTransactionDetails tx={tx} /> <EthTransactionDetails tx={tx} />
</> </>
); );
@@ -162,7 +148,7 @@ const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
{t('Your transaction has been completed.')}{' '} {t('Your transaction has been completed.')}{' '}
{isDeposit && t('Waiting for deposit confirmation.')} {isDeposit && t('Waiting for deposit confirmation.')}
</p> </p>
<EtherscanLink tx={tx} /> {tx.txHash && <EtherscanLink tx={tx.txHash} />}
<EthTransactionDetails tx={tx} /> <EthTransactionDetails tx={tx} />
</> </>
); );
+87 -51
View File
@@ -15,51 +15,29 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
})); }));
describe('AccountManager', () => { describe('AccountManager', () => {
beforeEach(() => { describe('when rerender', () => {
mockedUseDataProvider beforeEach(() => {
.mockImplementationOnce((args) => { mockedUseDataProvider
return { .mockImplementationOnce((args) => {
data: [], return {
}; data: [],
}) };
.mockImplementationOnce((args) => { })
return { .mockImplementationOnce((args) => {
data: [ return {
{ asset: { id: 'a1' }, party: { id: 't1' } }, data: [
{ asset: { id: 'a2' }, party: { id: 't2' } }, { asset: { id: 'a1' }, party: { id: 't1' } },
], { asset: { id: 'a2' }, party: { id: 't2' } },
}; ],
}); };
}); });
it('change partyId should reload data provider', async () => {
const { rerender } = render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables.partyId
).toEqual('partyOne');
await act(() => {
rerender(
<AccountManager
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
}); });
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables.partyId
).toEqual('partyTwo');
});
it('update method should return proper result', async () => { afterEach(() => {
let rerenderer: (ui: React.ReactElement) => void; jest.clearAllMocks();
await act(() => { });
it('change partyId should reload data provider', async () => {
const { rerender } = render( const { rerender } = render(
<AccountManager <AccountManager
partyId="partyOne" partyId="partyOne"
@@ -67,13 +45,67 @@ describe('AccountManager', () => {
isReadOnly={false} isReadOnly={false}
/> />
); );
rerenderer = rerender; expect(
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables
.partyId
).toEqual('partyOne');
await act(() => {
rerender(
<AccountManager
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
});
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables
.partyId
).toEqual('partyTwo');
}); });
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument(); it('update method should return proper result', async () => {
let rerenderer: (ui: React.ReactElement) => void;
await act(() => {
const { rerender } = render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
rerenderer = rerender;
});
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument();
});
await act(() => {
rerenderer(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
});
const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => {
expect(container).toBeInTheDocument();
});
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
});
});
it('splash loading should be displayed', async () => {
mockedUseDataProvider.mockImplementation((args) => {
return {
loading: true,
data: null,
};
}); });
await act(() => { await act(() => {
rerenderer( render(
<AccountManager <AccountManager
partyId="partyOne" partyId="partyOne"
onClickAsset={jest.fn} onClickAsset={jest.fn}
@@ -81,11 +113,15 @@ describe('AccountManager', () => {
/> />
); );
}); });
const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => { await waitFor(() => {
expect(container).toBeInTheDocument(); expect(
screen.getByText(
(content, element) =>
Boolean(
element?.className.endsWith('flex items-center justify-center')
) && content.startsWith('Loading')
)
).toBeInTheDocument();
}); });
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
}); });
}); });
+5 -1
View File
@@ -19,6 +19,7 @@ interface AccountManagerProps {
onClickDeposit?: (assetId?: string) => void; onClickDeposit?: (assetId?: string) => void;
isReadOnly: boolean; isReadOnly: boolean;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
noBottomPlaceholder?: boolean;
} }
export const AccountManager = ({ export const AccountManager = ({
@@ -28,6 +29,7 @@ export const AccountManager = ({
partyId, partyId,
isReadOnly, isReadOnly,
pinnedAsset, pinnedAsset,
noBottomPlaceholder,
}: AccountManagerProps) => { }: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]); const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -45,6 +47,7 @@ export const AccountManager = ({
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({ const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
gridRef, gridRef,
setId, setId,
disabled: noBottomPlaceholder,
}); });
const getRowHeight = useCallback( const getRowHeight = useCallback(
@@ -60,7 +63,8 @@ export const AccountManager = ({
onClickDeposit={onClickDeposit} onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw} onClickWithdraw={onClickWithdraw}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
noRowsOverlayComponent={() => null} suppressLoadingOverlay
suppressNoRowsOverlay
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
getRowHeight={getRowHeight} getRowHeight={getRowHeight}
{...bottomPlaceholderProps} {...bottomPlaceholderProps}
+13 -7
View File
@@ -59,13 +59,19 @@ export function createClient({
const timestamp = r?.headers.get('x-block-timestamp'); const timestamp = r?.headers.get('x-block-timestamp');
if (blockHeight && timestamp) { if (blockHeight && timestamp) {
const state = useHeaderStore.getState(); const state = useHeaderStore.getState();
useHeaderStore.setState({ const urlState = state[r.url];
...state, if (
[r.url]: { !urlState?.blockHeight ||
blockHeight: Number(blockHeight), urlState.blockHeight !== blockHeight
timestamp: new Date(Number(timestamp.slice(0, -6))), ) {
}, useHeaderStore.setState({
}); ...state,
[r.url]: {
blockHeight: Number(blockHeight),
timestamp: new Date(Number(timestamp.slice(0, -6))),
},
});
}
} }
return response; return response;
}); });
+2 -2
View File
@@ -1,4 +1,4 @@
import { ContractAddressLink } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type * as Schema from '@vegaprotocol/types'; import type * as Schema from '@vegaprotocol/types';
@@ -108,7 +108,7 @@ export const rows: Rows = [
return ( return (
<> <>
<ContractAddressLink address={asset.source.contractAddress} />{' '} <EtherscanLink address={asset.source.contractAddress} />{' '}
<CopyWithTooltip text={asset.source.contractAddress}> <CopyWithTooltip text={asset.source.contractAddress}>
<button title={t('Copy address to clipboard')}> <button title={t('Copy address to clipboard')}>
<Icon size={3} name="duplicate" /> <Icon size={3} name="duplicate" />
+4 -1
View File
@@ -46,7 +46,10 @@ addVegaWalletSubmitProposal();
addVegaWalletSubmitLiquidityProvision(); addVegaWalletSubmitLiquidityProvision();
addImportNodeWallets(); addImportNodeWallets();
export { mockConnectWallet } from './lib/commands/vega-wallet-connect'; export {
mockConnectWallet,
mockConnectWalletWithUserError,
} from './lib/commands/vega-wallet-connect';
export type { onMessage } from './lib/mock-ws'; export type { onMessage } from './lib/mock-ws';
export { aliasGQLQuery } from './lib/mock-gql'; export { aliasGQLQuery } from './lib/mock-gql';
export { aliasWalletQuery } from './lib/mock-rest'; export { aliasWalletQuery } from './lib/mock-rest';
@@ -35,6 +35,8 @@ function createNewMarketProposal(): ProposalSubmissionBody {
changes: { changes: {
decimalPlaces: '5', decimalPlaces: '5',
positionDecimalPlaces: '5', positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0',
lpPriceRange: '10', lpPriceRange: '10',
instrument: { instrument: {
name: 'Test market 1', name: 'Test market 1',
@@ -1,4 +1,7 @@
import { aliasWalletConnectQuery } from '../mock-rest'; import {
aliasWalletConnectQuery,
aliasWalletConnectWithUserError,
} from '../mock-rest';
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -20,6 +23,12 @@ export const mockConnectWallet = () => {
}); });
}; };
export const mockConnectWalletWithUserError = () => {
cy.mockWallet((req) => {
aliasWalletConnectWithUserError(req);
});
};
export function addVegaWalletConnect() { export function addVegaWalletConnect() {
Cypress.Commands.add('connectVegaWallet', (isMobile) => { Cypress.Commands.add('connectVegaWallet', (isMobile) => {
mockConnectWallet(); mockConnectWallet();
+20
View File
@@ -63,3 +63,23 @@ export const aliasWalletConnectQuery = (
}); });
} }
}; };
export const aliasWalletConnectWithUserError = (
req: CyHttpMessages.IncomingHttpRequest
) => {
if (hasMethod(req, 'client.connect_wallet')) {
req.alias = 'client.connect_wallet';
req.reply({
statusCode: 400,
body: {
jsonrpc: '2.0',
error: {
code: 3001,
data: 'the user rejected the wallet connection',
message: 'User error',
},
id: '0',
},
});
}
};
@@ -1,7 +1,7 @@
import type { Asset } from '@vegaprotocol/assets'; import type { Asset } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit'; import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils'; import { formatNumber } from '@vegaprotocol/utils';
import type { EthStoredTxState } from '@vegaprotocol/web3'; import type { EthStoredTxState } from '@vegaprotocol/web3';
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3'; import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
@@ -126,14 +126,10 @@ const ApprovalTxFeedback = ({
selectedAsset: Asset; selectedAsset: Asset;
allowance?: BigNumber; allowance?: BigNumber;
}) => { }) => {
const { ETHERSCAN_URL } = useEnvironment();
if (!tx) return null; if (!tx) return null;
const txLink = tx.txHash && ( const txLink = tx.txHash && (
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}> <EtherscanLink tx={tx.txHash}>{t('View on Etherscan')}</EtherscanLink>
{t('View on Etherscan')}
</ExternalLink>
); );
if (tx.status === EthTxStatus.Error) { if (tx.status === EthTxStatus.Error) {
+3 -10
View File
@@ -8,7 +8,6 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { Link } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@@ -16,14 +15,13 @@ import type {
TypedDataAgGrid, TypedDataAgGrid,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import type { DepositFieldsFragment } from './__generated__/Deposit'; import type { DepositFieldsFragment } from './__generated__/Deposit';
import { useEnvironment } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import { DepositStatusMapping } from '@vegaprotocol/types'; import { DepositStatusMapping } from '@vegaprotocol/types';
export const DepositsTable = forwardRef< export const DepositsTable = forwardRef<
AgGridReact, AgGridReact,
TypedDataAgGrid<DepositFieldsFragment> TypedDataAgGrid<DepositFieldsFragment>
>((props, ref) => { >((props, ref) => {
const { ETHERSCAN_URL } = useEnvironment();
return ( return (
<AgGrid <AgGrid
ref={ref} ref={ref}
@@ -77,14 +75,9 @@ export const DepositsTable = forwardRef<
if (!data) return null; if (!data) return null;
if (!value) return '-'; if (!value) return '-';
return ( return (
<Link <EtherscanLink tx={value} data-testid="etherscan-link">
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${value}`}
data-testid="etherscan-link"
target="_blank"
>
{truncateByChars(value)} {truncateByChars(value)}
</Link> </EtherscanLink>
); );
}} }}
/> />
@@ -1,7 +1,7 @@
import type { Asset } from '@vegaprotocol/assets'; import type { Asset } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit'; import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3'; import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
import { getFaucetError } from './get-faucet-error'; import { getFaucetError } from './get-faucet-error';
@@ -19,7 +19,6 @@ export const FaucetNotification = ({
selectedAsset, selectedAsset,
faucetTxId, faucetTxId,
}: FaucetNotificationProps) => { }: FaucetNotificationProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const tx = useEthTransactionStore((state) => { const tx = useEthTransactionStore((state) => {
return state.transactions.find((t) => t?.id === faucetTxId); return state.transactions.find((t) => t?.id === faucetTxId);
}); });
@@ -79,9 +78,9 @@ export const FaucetNotification = ({
</p> </p>
{tx.txHash && ( {tx.txHash && (
<p> <p>
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}> <EtherscanLink tx={tx.txHash}>
{t('View on Etherscan')} {t('View on Etherscan')}
</ExternalLink> </EtherscanLink>
</p> </p>
)} )}
</> </>
@@ -107,9 +106,9 @@ export const FaucetNotification = ({
</p> </p>
{tx.txHash && ( {tx.txHash && (
<p> <p>
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}> <EtherscanLink tx={tx.txHash}>
{t('View on Etherscan')} {t('View on Etherscan')}
</ExternalLink> </EtherscanLink>
</p> </p>
)} )}
</> </>
@@ -1,13 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEtherscanLink } from '../hooks';
export const ContractAddressLink = ({ address }: { address: string }) => {
const etherscanLink = useEtherscanLink();
const href = etherscanLink(`/address/${address}`);
return (
<Link href={href} target="_blank" title={t('View on etherscan')}>
{address}
</Link>
);
};
@@ -0,0 +1,38 @@
import { t } from '@vegaprotocol/i18n';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ComponentProps } from 'react';
import { ETHERSCAN_ADDRESS, ETHERSCAN_TX, useEtherscanLink } from '../hooks';
export const EtherscanLink = ({
address,
tx,
children,
...props
}: {
address?: string;
tx?: string;
} & ComponentProps<typeof ExternalLink>) => {
const etherscanLink = useEtherscanLink();
let href = '';
if ((!address && !tx) || (address && tx)) {
return null;
}
if (address) {
href = etherscanLink(ETHERSCAN_ADDRESS.replace(':hash', address));
}
if (tx) {
href = etherscanLink(ETHERSCAN_TX.replace(':hash', tx));
}
return (
<ExternalLink
href={href}
title={t('View on Etherscan (opens in a new tab)')}
{...props}
>
{children || address || tx}
</ExternalLink>
);
};
+1 -1
View File
@@ -2,4 +2,4 @@ export * from './network-loader';
export * from './network-switcher'; export * from './network-switcher';
export * from './node-guard'; export * from './node-guard';
export * from './node-switcher'; export * from './node-switcher';
export * from './contract-address-link'; export * from './etherscan-link';
+1
View File
@@ -103,6 +103,7 @@ export const TOKEN_VALIDATOR = '/validators/:id';
export const EXPLORER_TX = '/txs/:hash'; export const EXPLORER_TX = '/txs/:hash';
// Etherscan pages // Etherscan pages
export const ETHERSCAN_ADDRESS = '/address/:hash';
export const ETHERSCAN_TX = '/tx/:hash'; export const ETHERSCAN_TX = '/tx/:hash';
// Console pages // Console pages
@@ -5,6 +5,7 @@ import { MockedProvider } from '@apollo/react-testing';
import type { StatisticsQuery } from '../utils/__generated__/Node'; import type { StatisticsQuery } from '../utils/__generated__/Node';
import { StatisticsDocument } from '../utils/__generated__/Node'; import { StatisticsDocument } from '../utils/__generated__/Node';
import { useHeaderStore } from '@vegaprotocol/apollo-client'; import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { Intent } from '@vegaprotocol/ui-toolkit';
const vegaUrl = 'https://foo.bar.com'; const vegaUrl = 'https://foo.bar.com';
@@ -55,9 +56,24 @@ function setup(
describe('useNodeHealth', () => { describe('useNodeHealth', () => {
it.each([ it.each([
{ core: 1, node: 1, expected: 0 }, {
{ core: 1, node: 5, expected: -4 }, core: 1,
{ core: 10, node: 5, expected: 5 }, node: 1,
expectedText: 'Operational',
expectedIntent: Intent.Success,
},
{
core: 1,
node: 5,
expectedText: 'Operational',
expectedIntent: Intent.Success,
},
{
core: 10,
node: 5,
expectedText: '5 Blocks behind',
expectedIntent: Intent.Warning,
},
])( ])(
'provides difference core block $core and node block $node', 'provides difference core block $core and node block $node',
async (cases) => { async (cases) => {
@@ -65,12 +81,12 @@ describe('useNodeHealth', () => {
blockHeight: cases.node, blockHeight: cases.node,
timestamp: new Date(), timestamp: new Date(),
}); });
expect(result.current.blockDiff).toEqual(null); expect(result.current.text).toEqual('Non operational');
expect(result.current.coreBlockHeight).toEqual(undefined); expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(cases.node); expect(result.current.datanodeBlockHeight).toEqual(cases.node);
await waitFor(() => { await waitFor(() => {
expect(result.current.blockDiff).toEqual(cases.expected); expect(result.current.text).toEqual(cases.expectedText);
expect(result.current.coreBlockHeight).toEqual(cases.core); expect(result.current.intent).toEqual(cases.expectedIntent);
expect(result.current.datanodeBlockHeight).toEqual(cases.node); expect(result.current.datanodeBlockHeight).toEqual(cases.node);
}); });
} }
@@ -90,25 +106,64 @@ describe('useNodeHealth', () => {
blockHeight: 1, blockHeight: 1,
timestamp: new Date(), timestamp: new Date(),
}); });
expect(result.current.blockDiff).toEqual(null); expect(result.current.text).toEqual('Non operational');
expect(result.current.coreBlockHeight).toEqual(undefined); expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(1); expect(result.current.datanodeBlockHeight).toEqual(1);
await waitFor(() => { await waitFor(() => {
expect(result.current.blockDiff).toEqual(null); expect(result.current.text).toEqual('Non operational');
expect(result.current.coreBlockHeight).toEqual(undefined); expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(1); expect(result.current.datanodeBlockHeight).toEqual(1);
}); });
}); });
it('returns 0 if no headers are found (waits until stats query resolves)', async () => { it('returns 0 if no headers are found (waits until stats query resolves)', async () => {
const { result } = setup(createStatsMock(1), undefined); const { result } = setup(createStatsMock(1), undefined);
expect(result.current.blockDiff).toEqual(null); expect(result.current.text).toEqual('Non operational');
expect(result.current.coreBlockHeight).toEqual(undefined); expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(undefined); expect(result.current.datanodeBlockHeight).toEqual(undefined);
await waitFor(() => { await waitFor(() => {
expect(result.current.blockDiff).toEqual(0); expect(result.current.text).toEqual('Operational');
expect(result.current.coreBlockHeight).toEqual(1); expect(result.current.intent).toEqual(Intent.Success);
expect(result.current.datanodeBlockHeight).toEqual(undefined); expect(result.current.datanodeBlockHeight).toEqual(undefined);
}); });
}); });
it('Warning latency', async () => {
const now = 1678800900087;
const headerTimestamp = now - 4000;
const dateNow = new Date(now);
const dateHeaderTimestamp = new Date(headerTimestamp);
jest.useFakeTimers().setSystemTime(dateNow);
const { result } = setup(createStatsMock(2), {
blockHeight: 2,
timestamp: dateHeaderTimestamp,
});
await waitFor(() => {
expect(result.current.text).toEqual('Warning delay ( >3 sec): 4.05 sec');
expect(result.current.intent).toEqual(Intent.Warning);
expect(result.current.datanodeBlockHeight).toEqual(2);
});
});
it('Erroneous latency', async () => {
const now = 1678800900087;
const headerTimestamp = now - 11000;
const dateNow = new Date(now);
const dateHeaderTimestamp = new Date(headerTimestamp);
jest.useFakeTimers().setSystemTime(dateNow);
const { result } = setup(createStatsMock(2), {
blockHeight: 2,
timestamp: dateHeaderTimestamp,
});
await waitFor(() => {
expect(result.current.text).toEqual(
'Erroneous latency ( >10 sec): 11.05 sec'
);
expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(2);
});
jest.useRealTimers();
});
}); });
+48 -17
View File
@@ -2,30 +2,35 @@ import { useEffect, useMemo } from 'react';
import { useStatisticsQuery } from '../utils/__generated__/Node'; import { useStatisticsQuery } from '../utils/__generated__/Node';
import { useHeaderStore } from '@vegaprotocol/apollo-client'; import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { useEnvironment } from './use-environment'; import { useEnvironment } from './use-environment';
import { fromNanoSeconds } from '@vegaprotocol/utils'; import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
const POLL_INTERVAL = 1000; const POLL_INTERVAL = 1000;
const BLOCK_THRESHOLD = 3;
const ERROR_LATENCY = 10000;
const WARNING_LATENCY = 3000;
export const useNodeHealth = () => { export const useNodeHealth = () => {
const online = useNavigatorOnline();
const url = useEnvironment((store) => store.VEGA_URL); const url = useEnvironment((store) => store.VEGA_URL);
const headerStore = useHeaderStore(); const headerStore = useHeaderStore();
const headers = url ? headerStore[url] : undefined; const headers = url ? headerStore[url] : undefined;
const { data, error, loading, startPolling, stopPolling } = const { data, error, startPolling, stopPolling } = useStatisticsQuery({
useStatisticsQuery({ fetchPolicy: 'no-cache',
fetchPolicy: 'no-cache', });
});
const blockDiff = useMemo(() => { const blockDiff = useMemo(() => {
if (!data?.statistics.blockHeight) { if (!data?.statistics.blockHeight) {
return null; return null;
} }
if (!headers) { if (!headers?.blockHeight) {
return 0; return 0;
} }
return Number(data.statistics.blockHeight) - headers.blockHeight; return Number(data.statistics.blockHeight) - headers.blockHeight;
}, [data, headers]); }, [data?.statistics.blockHeight, headers?.blockHeight]);
useEffect(() => { useEffect(() => {
if (error) { if (error) {
@@ -38,17 +43,43 @@ export const useNodeHealth = () => {
} }
}, [error, startPolling, stopPolling]); }, [error, startPolling, stopPolling]);
const blockUpdateMsLatency = headers?.timestamp
? Date.now() - headers.timestamp.getTime()
: 0;
const [text, intent] = useMemo(() => {
let intent = Intent.Success;
let text = 'Operational';
if (!online) {
text = t('Offline');
intent = Intent.Danger;
} else if (blockDiff === null) {
// Block height query failed and null was returned
text = t('Non operational');
intent = Intent.Danger;
} else if (blockUpdateMsLatency > ERROR_LATENCY) {
text = t('Erroneous latency ( >%s sec): %s sec', [
(ERROR_LATENCY / 1000).toString(),
(blockUpdateMsLatency / 1000).toFixed(2),
]);
intent = Intent.Danger;
} else if (blockDiff >= BLOCK_THRESHOLD) {
text = t(`%s Blocks behind`, String(blockDiff));
intent = Intent.Warning;
} else if (blockUpdateMsLatency > WARNING_LATENCY) {
text = t('Warning delay ( >%s sec): %s sec', [
(WARNING_LATENCY / 1000).toString(),
(blockUpdateMsLatency / 1000).toFixed(2),
]);
intent = Intent.Warning;
}
return [text, intent];
}, [online, blockDiff, blockUpdateMsLatency]);
return { return {
error,
loading,
coreBlockHeight: data?.statistics
? Number(data.statistics.blockHeight)
: undefined,
coreVegaTime: data?.statistics
? fromNanoSeconds(data?.statistics.vegaTime)
: undefined,
datanodeBlockHeight: headers?.blockHeight, datanodeBlockHeight: headers?.blockHeight,
datanodeVegaTime: headers?.timestamp, text,
blockDiff, intent,
}; };
}; };
@@ -3,3 +3,4 @@ export * from './info-market';
export * from './tooltip-mapping'; export * from './tooltip-mapping';
export * from './__generated__/MarketInfo'; export * from './__generated__/MarketInfo';
export * from './market-info-data-provider'; export * from './market-info-data-provider';
export * from './market-info-panels';
@@ -1,17 +1,5 @@
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import { removePaginationWrapper, TokenLinks } from '@vegaprotocol/utils';
totalFeesPercentage,
calcCandleVolume,
} from '@vegaprotocol/market-list';
import {
addDecimalsFormatNumber,
formatNumber,
formatNumberPercentage,
removePaginationWrapper,
TokenLinks,
getMarketExpiryDateFormatted,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers'; import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
@@ -22,15 +10,31 @@ import {
Link as UILink, Link as UILink,
Splash, Splash,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { generatePath, Link } from 'react-router-dom'; import { generatePath, Link } from 'react-router-dom';
import { MarketInfoTable } from './info-key-value-table';
import { marketInfoWithDataAndCandlesProvider } from './market-info-data-provider'; import { marketInfoWithDataAndCandlesProvider } from './market-info-data-provider';
import type { MarketInfoWithDataAndCandles } from './market-info-data-provider'; import type { MarketInfoWithDataAndCandles } from './market-info-data-provider';
import { MarketProposalNotification } from '@vegaprotocol/proposals'; import { MarketProposalNotification } from '@vegaprotocol/proposals';
import {
CurrentFeesInfoPanel,
InstrumentInfoPanel,
InsurancePoolInfoPanel,
KeyDetailsInfoPanel,
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
LiquidityPriceRangeInfoPanel,
MarketPriceInfoPanel,
MarketVolumeInfoPanel,
MetadataInfoPanel,
OracleInfoPanel,
PriceMonitoringBoundsInfoPanel,
RiskFactorsInfoPanel,
RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel,
} from './market-info-panels';
export interface InfoProps { export interface InfoProps {
market: MarketInfoWithDataAndCandles; market: MarketInfoWithDataAndCandles;
@@ -80,15 +84,6 @@ export const MarketInfoContainer = ({
export const Info = ({ market, onSelect }: InfoProps) => { export const Info = ({ market, onSelect }: InfoProps) => {
const { VEGA_TOKEN_URL, VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_TOKEN_URL, VEGA_EXPLORER_URL } = useEnvironment();
const headerClassName = 'uppercase text-lg'; const headerClassName = 'uppercase text-lg';
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
);
const { data: asset } = useAssetDataProvider(assetId ?? '');
if (!market) return null; if (!market) return null;
@@ -96,272 +91,75 @@ export const Info = ({ market, onSelect }: InfoProps) => {
market.accountsConnection?.edges market.accountsConnection?.edges
); );
const last24hourVolume = market.candles && calcCandleVolume(market.candles);
const marketDataPanels = [ const marketDataPanels = [
{ {
title: t('Current fees'), title: t('Current fees'),
content: ( content: <CurrentFeesInfoPanel market={market} />,
<>
<MarketInfoTable
data={{
...market.fees.factors,
totalFees: totalFeesPercentage(market.fees.factors),
}}
asPercentage={true}
/>
<p className="text-xs">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
),
}, },
{ {
title: t('Market price'), title: t('Market price'),
content: ( content: <MarketPriceInfoPanel market={market} />,
<>
<MarketInfoTable
data={{
markPrice: market.data?.markPrice,
bestBidPrice: market.data?.bestBidPrice,
bestOfferPrice: market.data?.bestOfferPrice,
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}}
decimalPlaces={market.decimalPlaces}
/>
<p className="text-xs mt-4">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
)}
</p>
</>
),
}, },
{ {
title: t('Market volume'), title: t('Market volume'),
content: ( content: <MarketVolumeInfoPanel market={market} />,
<MarketInfoTable
data={{
'24hourVolume':
last24hourVolume && last24hourVolume !== '0'
? addDecimalsFormatNumber(
last24hourVolume,
market.positionDecimalPlaces
)
: '-',
openInterest: market.data?.openInterest,
bestBidVolume: market.data?.bestBidVolume,
bestOfferVolume: market.data?.bestOfferVolume,
bestStaticBidVolume: market.data?.bestStaticBidVolume,
bestStaticOfferVolume: market.data?.bestStaticOfferVolume,
}}
decimalPlaces={market.positionDecimalPlaces}
/>
),
}, },
...marketAccounts ...marketAccounts
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE) .filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
.map((a) => ({ .map((a) => ({
title: t(`Insurance pool`), title: t(`Insurance pool`),
content: ( content: <InsurancePoolInfoPanel market={market} account={a} />,
<MarketInfoTable
data={{
balance: a.balance,
}}
assetSymbol={assetSymbol}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/>
),
})), })),
]; ];
const keyDetails = {
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
tradingMode: market.tradingMode,
state: Schema.MarketStateMapping[market.state],
};
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const marketSpecPanels = [ const marketSpecPanels = [
{ {
title: t('Key details'), title: t('Key details'),
content: ( content: <KeyDetailsInfoPanel market={market} />,
<MarketInfoTable
data={{
name: market.tradableInstrument.instrument.name,
marketID: market.id,
tradingMode:
keyDetails.tradingMode &&
Schema.MarketTradingModeMapping[keyDetails.tradingMode],
marketDecimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
settlementAssetDecimalPlaces: assetDecimals,
}}
/>
),
}, },
{ {
title: t('Instrument'), title: t('Instrument'),
content: ( content: <InstrumentInfoPanel market={market} />,
<MarketInfoTable
data={{
marketName: market.tradableInstrument.instrument.name,
code: market.tradableInstrument.instrument.code,
productType:
market.tradableInstrument.instrument.product.__typename,
...market.tradableInstrument.instrument.product,
}}
/>
),
}, },
{ {
title: t('Settlement asset'), title: t('Settlement asset'),
content: asset ? ( content: <SettlementAssetInfoPanel market={market} />,
<>
<AssetDetailsTable
asset={asset}
inline={true}
noBorder={true}
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/>
<p className="text-xs mt-4">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
)}
</p>
</>
) : (
<Splash>{t('No data')}</Splash>
),
}, },
{ {
title: t('Metadata'), title: t('Metadata'),
content: ( content: <MetadataInfoPanel market={market} />,
<MarketInfoTable
data={{
expiryDate: getMarketExpiryDateFormatted(
market.tradableInstrument.instrument.metadata.tags
),
...market.tradableInstrument.instrument.metadata.tags
?.map((tag) => {
const [key, value] = tag.split(':');
return { [key]: value };
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
}}
/>
),
}, },
{ {
title: t('Risk model'), title: t('Risk model'),
content: ( content: <RiskModelInfoPanel market={market} />,
<MarketInfoTable
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
/>
),
}, },
{ {
title: t('Risk parameters'), title: t('Risk parameters'),
content: ( content: <RiskParametersInfoPanel market={market} />,
<MarketInfoTable
data={market.tradableInstrument.riskModel.params}
unformatted={true}
omits={[]}
/>
),
}, },
{ {
title: t('Risk factors'), title: t('Risk factors'),
content: ( content: <RiskFactorsInfoPanel market={market} />,
<MarketInfoTable
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
/>
),
}, },
...(market.priceMonitoringSettings?.parameters?.triggers || []).map( ...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => { (_, triggerIndex) => ({
const bounds = market.data?.priceMonitoringBounds?.[i]; title: t(`Price monitoring bounds ${triggerIndex + 1}`),
return { content: (
title: t(`Price monitoring bounds ${i + 1}`), <PriceMonitoringBoundsInfoPanel
content: ( market={market}
<div className="text-xs"> triggerIndex={triggerIndex}
<div className="grid grid-cols-2 text-xs mb-4"> />
<p className="col-span-1"> ),
{t('%s probability price bounds', [ })
formatNumberPercentage(
new BigNumber(trigger.probability).times(100)
),
])}
</p>
<p className="col-span-1 text-right">
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
</p>
</div>
<div className="pl-2 pb-0 text-xs border-l-2">
{bounds && (
<MarketInfoTable
data={{
highestPrice: bounds.maxValidPrice,
lowestPrice: bounds.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
assetSymbol={quoteUnit}
/>
)}
</div>
<p className="mt-4">
{t('Results in %s seconds auction if breached', [
trigger.auctionExtensionSecs.toString(),
])}
</p>
</div>
),
};
}
), ),
{ {
title: t('Liquidity monitoring parameters'), title: t('Liquidity monitoring parameters'),
content: ( content: <LiquidityMonitoringParametersInfoPanel market={market} />,
<MarketInfoTable
data={{
triggeringRatio:
market.liquidityMonitoringParameters.triggeringRatio,
...market.liquidityMonitoringParameters.targetStakeParameters,
}}
/>
),
}, },
{ {
title: t('Liquidity'), title: t('Liquidity'),
content: ( content: (
<MarketInfoTable <LiquidityInfoPanel market={market}>
data={{
targetStake: market.data && market.data.targetStake,
suppliedStake: market.data && market.data?.suppliedStake,
marketValueProxy: market.data && market.data.marketValueProxy,
}}
decimalPlaces={assetDecimals}
assetSymbol={assetSymbol}
>
<Link <Link
to={`/liquidity/${market.id}`} to={`/liquidity/${market.id}`}
onClick={() => onSelect(market.id)} onClick={() => onSelect(market.id)}
@@ -369,57 +167,17 @@ export const Info = ({ market, onSelect }: InfoProps) => {
> >
<UILink>{t('View liquidity provision table')}</UILink> <UILink>{t('View liquidity provision table')}</UILink>
</Link> </Link>
</MarketInfoTable> </LiquidityInfoPanel>
), ),
}, },
{ {
title: t('Liquidity price range'), title: t('Liquidity price range'),
content: ( content: <LiquidityPriceRangeInfoPanel market={market} />,
<>
<p className="text-xs mb-4">
{`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`}
</p>
<p className="text-xs mb-4">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<div className="pl-2 pb-0 text-xs border-l-2">
<MarketInfoTable
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
></MarketInfoTable>
</div>
</>
),
}, },
{ {
title: t('Oracle'), title: t('Oracle'),
content: ( content: (
<MarketInfoTable <OracleInfoPanel market={market}>
data={
market.tradableInstrument.instrument.product.dataSourceSpecBinding
}
>
<ExternalLink <ExternalLink
href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`} href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
> >
@@ -430,7 +188,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
> >
{t('View termination oracle specification')} {t('View termination oracle specification')}
</ExternalLink> </ExternalLink>
</MarketInfoTable> </OracleInfoPanel>
), ),
}, },
]; ];
@@ -0,0 +1,418 @@
import type { ComponentProps } from 'react';
import { useMemo } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import {
calcCandleVolume,
totalFeesPercentage,
} from '@vegaprotocol/market-list';
import { Splash } from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
formatNumber,
formatNumberPercentage,
getMarketExpiryDateFormatted,
} from '@vegaprotocol/utils';
import type { Get } from 'type-fest';
import { MarketInfoTable } from './info-key-value-table';
import type {
MarketInfo,
MarketInfoWithData,
MarketInfoWithDataAndCandles,
} from './market-info-data-provider';
import BigNumber from 'bignumber.js';
import { MarketTradingModeMapping } from '@vegaprotocol/types';
type PanelProps = Pick<
ComponentProps<typeof MarketInfoTable>,
'children' | 'noBorder'
>;
type MarketInfoProps = {
market: MarketInfo;
};
type MarketInfoWithDataProps = {
market: MarketInfoWithData;
};
type MarketInfoWithDataAndCandlesProps = {
market: MarketInfoWithDataAndCandles;
};
export const CurrentFeesInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<>
<MarketInfoTable
data={{
...market.fees.factors,
totalFees: totalFeesPercentage(market.fees.factors),
}}
asPercentage={true}
{...props}
/>
<p className="text-xs">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
);
export const MarketPriceInfoPanel = ({
market,
...props
}: MarketInfoWithDataProps & PanelProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
return (
<>
<MarketInfoTable
data={{
markPrice: market.data?.markPrice,
bestBidPrice: market.data?.bestBidPrice,
bestOfferPrice: market.data?.bestOfferPrice,
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}}
decimalPlaces={market.decimalPlaces}
{...props}
/>
<p className="text-xs mt-4">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
)}
</p>
</>
);
};
export const MarketVolumeInfoPanel = ({
market,
...props
}: MarketInfoWithDataAndCandlesProps & PanelProps) => {
const last24hourVolume = market.candles && calcCandleVolume(market.candles);
return (
<MarketInfoTable
data={{
'24hourVolume':
last24hourVolume && last24hourVolume !== '0'
? addDecimalsFormatNumber(
last24hourVolume,
market.positionDecimalPlaces
)
: '-',
openInterest: market.data?.openInterest,
bestBidVolume: market.data?.bestBidVolume,
bestOfferVolume: market.data?.bestOfferVolume,
bestStaticBidVolume: market.data?.bestStaticBidVolume,
bestStaticOfferVolume: market.data?.bestStaticOfferVolume,
}}
decimalPlaces={market.positionDecimalPlaces}
{...props}
/>
);
};
export const InsurancePoolInfoPanel = ({
market,
account,
...props
}: {
account: NonNullable<
Get<MarketInfoWithData, 'accountsConnection.edges[0].node'>
>;
} & MarketInfoProps &
PanelProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
return (
<MarketInfoTable
data={{
balance: account.balance,
}}
assetSymbol={assetSymbol}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset.decimals
}
{...props}
/>
);
};
export const KeyDetailsInfoPanel = ({
market,
}: MarketInfoProps & PanelProps) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
return (
<MarketInfoTable
data={{
name: market.tradableInstrument.instrument.name,
marketID: market.id,
tradingMode:
market.tradingMode && MarketTradingModeMapping[market.tradingMode],
marketDecimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
settlementAssetDecimalPlaces: assetDecimals,
}}
/>
);
};
export const InstrumentInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={{
marketName: market.tradableInstrument.instrument.name,
code: market.tradableInstrument.instrument.code,
productType: market.tradableInstrument.instrument.product.__typename,
...market.tradableInstrument.instrument.product,
}}
{...props}
/>
);
export const SettlementAssetInfoPanel = ({
market,
noBorder = true,
}: MarketInfoProps & PanelProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
);
const { data: asset } = useAssetDataProvider(assetId ?? '');
return asset ? (
<>
<AssetDetailsTable
asset={asset}
inline={true}
noBorder={noBorder}
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/>
<p className="text-xs mt-4">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
)}
</p>
</>
) : (
<Splash>{t('No data')}</Splash>
);
};
export const MetadataInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={{
expiryDate: getMarketExpiryDateFormatted(
market.tradableInstrument.instrument.metadata.tags
),
...market.tradableInstrument.instrument.metadata.tags
?.map((tag) => {
const [key, value] = tag.split(':');
return { [key]: value };
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
}}
{...props}
/>
);
export const RiskModelInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
{...props}
/>
);
export const RiskParametersInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={market.tradableInstrument.riskModel.params}
unformatted={true}
omits={[]}
{...props}
/>
);
export const RiskFactorsInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
{...props}
/>
);
export const PriceMonitoringBoundsInfoPanel = ({
market,
triggerIndex,
...props
}: {
triggerIndex: number;
} & MarketInfoWithDataProps &
PanelProps) => {
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const trigger =
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
const bounds = market.data?.priceMonitoringBounds?.[triggerIndex];
if (!trigger) {
console.error(
`Could not find data for trigger ${triggerIndex} (market id: ${market.id})`
);
return null;
}
return (
<div className="text-xs">
<div className="grid grid-cols-2 text-xs mb-4">
<p className="col-span-1">
{t('%s probability price bounds', [
formatNumberPercentage(
new BigNumber(trigger.probability).times(100)
),
])}
</p>
<p className="col-span-1 text-right">
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
</p>
</div>
<div className="pl-2 pb-0 text-xs border-l-2">
{bounds && (
<MarketInfoTable
data={{
highestPrice: bounds.maxValidPrice,
lowestPrice: bounds.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
assetSymbol={quoteUnit}
{...props}
/>
)}
</div>
<p className="mt-4">
{t('Results in %s seconds auction if breached', [
trigger.auctionExtensionSecs.toString(),
])}
</p>
</div>
);
};
export const LiquidityMonitoringParametersInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={{
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
...market.liquidityMonitoringParameters.targetStakeParameters,
}}
{...props}
/>
);
export const LiquidityInfoPanel = ({
market,
...props
}: MarketInfoWithDataProps & PanelProps) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
return (
<MarketInfoTable
data={{
targetStake: market.data && market.data.targetStake,
suppliedStake: market.data && market.data?.suppliedStake,
marketValueProxy: market.data && market.data.marketValueProxy,
}}
decimalPlaces={assetDecimals}
assetSymbol={assetSymbol}
{...props}
/>
);
};
export const LiquidityPriceRangeInfoPanel = ({
market,
...props
}: MarketInfoWithDataProps & PanelProps) => {
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
return (
<>
<p className="text-xs mb-4">
{`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`}
</p>
<p className="text-xs mb-4">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<div className="pl-2 pb-0 text-xs border-l-2">
<MarketInfoTable
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
></MarketInfoTable>
</div>
</>
);
};
export const OracleInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={market.tradableInstrument.instrument.product.dataSourceSpecBinding}
{...props}
/>
);
+15 -5
View File
@@ -1,7 +1,8 @@
import { useCallback, useRef } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { Position } from '../'; import type { Position } from '../';
import { usePositionsData, PositionsTable } from '../'; import { usePositionsData, PositionsTable } from '../';
import type { FilterChangedEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { useVegaTransactionStore } from '@vegaprotocol/wallet'; import { useVegaTransactionStore } from '@vegaprotocol/wallet';
@@ -22,6 +23,7 @@ export const PositionsManager = ({
noBottomPlaceholder, noBottomPlaceholder,
}: PositionsManagerProps) => { }: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = usePositionsData( const { data, error, loading, reload } = usePositionsData(
partyId, partyId,
gridRef, gridRef,
@@ -66,8 +68,14 @@ export const PositionsManager = ({
const bottomPlaceholderProps = useBottomPlaceholder<Position>({ const bottomPlaceholderProps = useBottomPlaceholder<Position>({
gridRef, gridRef,
setId, setId,
disabled: noBottomPlaceholder,
}); });
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data?.length]);
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
return ( return (
<div className="h-full relative"> <div className="h-full relative">
<PositionsTable <PositionsTable
@@ -75,9 +83,11 @@ export const PositionsManager = ({
ref={gridRef} ref={gridRef}
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onClose={onClose} onClose={onClose}
noRowsOverlayComponent={() => null} suppressLoadingOverlay
suppressNoRowsOverlay
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
{...(noBottomPlaceholder ? null : bottomPlaceholderProps)} onFilterChanged={onFilterChanged}
{...bottomPlaceholderProps}
/> />
<div className="pointer-events-none absolute inset-0"> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer <AsyncRenderer
@@ -85,7 +95,7 @@ export const PositionsManager = ({
error={error} error={error}
data={data} data={data}
noDataMessage={t('No positions')} noDataMessage={t('No positions')}
noDataCondition={(data) => !(data && data.length)} noDataCondition={(data) => !dataCount}
reload={reload} reload={reload}
/> />
</div> </div>
+16 -1
View File
@@ -116,6 +116,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
value value
) )
} }
minWidth={190}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Notional')} headerName={t('Notional')}
@@ -141,6 +142,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces data.marketDecimalPlaces
); );
}} }}
minWidth={80}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Open volume')} headerName={t('Open volume')}
@@ -174,6 +176,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
); );
}} }}
cellRenderer={OpenVolumeCell} cellRenderer={OpenVolumeCell}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Mark price')} headerName={t('Mark price')}
@@ -213,8 +216,13 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces data.marketDecimalPlaces
); );
}} }}
minWidth={100}
/>
<AgGridColumn
headerName={t('Settlement asset')}
field="assetSymbol"
minWidth={100}
/> />
<AgGridColumn headerName={t('Settlement asset')} field="assetSymbol" />
<AgGridColumn <AgGridColumn
headerName={t('Entry price')} headerName={t('Entry price')}
field="averageEntryPrice" field="averageEntryPrice"
@@ -248,6 +256,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces data.marketDecimalPlaces
); );
}} }}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Leverage')} headerName={t('Leverage')}
@@ -264,6 +273,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
}: VegaValueFormatterParams<Position, 'currentLeverage'>) => }: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
value === undefined ? undefined : formatNumber(value.toString(), 1) value === undefined ? undefined : formatNumber(value.toString(), 1)
} }
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Margin allocated')} headerName={t('Margin allocated')}
@@ -295,6 +305,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.decimals data.decimals
); );
}} }}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Realised PNL')} headerName={t('Realised PNL')}
@@ -321,6 +332,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.' 'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
)} )}
cellRenderer={PNLCell} cellRenderer={PNLCell}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Unrealised PNL')} headerName={t('Unrealised PNL')}
@@ -347,6 +359,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.' 'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
)} )}
cellRenderer={PNLCell} cellRenderer={PNLCell}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Updated')} headerName={t('Updated')}
@@ -361,6 +374,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
} }
return getDateTimeFormat().format(new Date(value)); return getDateTimeFormat().format(new Date(value));
}} }}
minWidth={150}
/> />
{onClose && !props.isReadOnly ? ( {onClose && !props.isReadOnly ? (
<AgGridColumn <AgGridColumn
@@ -375,6 +389,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
</ButtonLink> </ButtonLink>
) : null ) : null
} }
minWidth={80}
/> />
) : null} ) : null}
</AgGrid> </AgGrid>
@@ -11,11 +11,13 @@ const isFullWidthRow = (params: IsFullWidthRowParams) =>
interface Props<T> { interface Props<T> {
gridRef: RefObject<AgGridReact>; gridRef: RefObject<AgGridReact>;
setId?: (data: T) => T; setId?: (data: T) => T;
disabled?: boolean;
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
export const useBottomPlaceholder = <T extends {}>({ export const useBottomPlaceholder = <T extends {}>({
gridRef, gridRef,
setId, setId,
disabled,
}: Props<T>) => { }: Props<T>) => {
const onBodyScrollEnd = useCallback(() => { const onBodyScrollEnd = useCallback(() => {
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0; const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
@@ -58,14 +60,17 @@ export const useBottomPlaceholder = <T extends {}>({
}, [gridRef, onBodyScrollEnd]); }, [gridRef, onBodyScrollEnd]);
return useMemo( return useMemo(
() => ({ () =>
onBodyScrollEnd, !disabled
rowClassRules: NO_HOVER_CSS_RULE, ? {
isFullWidthRow, onBodyScrollEnd,
fullWidthCellRenderer, rowClassRules: NO_HOVER_CSS_RULE,
onSortChanged: onRowsChanged, isFullWidthRow,
onFilterChange: onRowsChanged, fullWidthCellRenderer,
}), onSortChanged: onRowsChanged,
[onBodyScrollEnd, onRowsChanged] onFilterChange: onRowsChanged,
}
: {},
[onBodyScrollEnd, onRowsChanged, disabled]
); );
}; };
+13 -4
View File
@@ -1,10 +1,19 @@
import { useRef, useEffect, useState } from 'react'; import { useRef, useEffect, useState } from 'react';
const SERVER_SIDE_DIMENSIONS = {
width: 1200,
height: 900,
};
export const useResize = () => { export const useResize = () => {
const [windowSize, setWindowSize] = useState({ const [windowSize, setWindowSize] = useState(
width: window.innerWidth, typeof window !== undefined
height: window.innerHeight, ? {
}); width: window.innerWidth,
height: window.innerHeight,
}
: { ...SERVER_SIDE_DIMENSIONS }
);
const timeout = useRef(0); const timeout = useRef(0);
@@ -1,29 +1,31 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
// @ts-ignore avoid adding declaration file
import { theme } from '@vegaprotocol/tailwindcss-config'; import { theme } from '@vegaprotocol/tailwindcss-config';
import { useResize } from './use-resize'; import { useResize } from './use-resize';
type Screen = keyof typeof theme.screens; export type Screen = keyof typeof theme.screens;
interface Props { interface Props {
isMobile: boolean; isMobile: boolean;
screenSize: Screen; screenSize: Screen;
width: number;
} }
export const useScreenDimensions = (): Props => { export const useScreenDimensions = (): Props => {
const { width } = useResize(); const { width } = useResize();
const isMobile = width < parseInt(theme.screens.md);
const screenSize = Object.entries(theme.screens).reduce(
(agg: Screen, entry) => {
if (width > parseInt(entry[1])) {
agg = entry[0] as Screen;
}
return agg;
},
'xs'
);
return useMemo( return useMemo(
() => ({ () => ({
width, isMobile,
isMobile: width < parseInt(theme.screens.md), screenSize,
screenSize: Object.entries(theme.screens).reduce((agg: Screen, entry) => {
if (width > parseInt(entry[1])) {
agg = entry[0] as Screen;
}
return agg;
}, 'xs'),
}), }),
[width] [isMobile, screenSize]
); );
}; };
+1
View File
@@ -6,6 +6,7 @@ module.exports = {
lg: '960px', lg: '960px',
xl: '1280px', xl: '1280px',
xxl: '1536px', xxl: '1536px',
xxxl: '1800px',
}, },
colors: { colors: {
transparent: 'transparent', transparent: 'transparent',
+1 -1
View File
@@ -50,7 +50,7 @@ interface Props extends AgGridReactProps {
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => { export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
return ( return (
<AgGrid <AgGrid
style={{ width: '100%', height: '100%', background: 'red' }} style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No trades')} overlayNoRowsTemplate={t('No trades')}
getRowId={({ data }) => data.id} getRowId={({ data }) => data.id}
ref={ref} ref={ref}
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"name": "@vegaprotocol/ui-toolkit", "name": "@vegaprotocol/ui-toolkit",
"version": "0.7.0" "version": "0.8.0"
} }
@@ -4,8 +4,6 @@ import type {
ReactNode, ReactNode,
} from 'react'; } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import type { IconName } from '../icon';
import { Icon } from '../icon';
import classnames from 'classnames'; import classnames from 'classnames';
export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary'; export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary';
@@ -76,8 +74,8 @@ interface CommonProps {
disabled?: boolean; disabled?: boolean;
fill?: boolean; fill?: boolean;
size?: ButtonSize; size?: ButtonSize;
icon?: IconName; icon?: ReactNode;
rightIcon?: IconName; rightIcon?: ReactNode;
} }
export interface ButtonProps export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>, extends ButtonHTMLAttributes<HTMLButtonElement>,
@@ -151,17 +149,13 @@ export const ButtonLink = forwardRef<HTMLButtonElement, ButtonLinkProps>(
interface ButtonContentProps { interface ButtonContentProps {
children: ReactNode; children: ReactNode;
icon?: IconName; icon?: ReactNode;
rightIcon?: IconName; rightIcon?: ReactNode;
} }
const ButtonContent = ({ children, icon, rightIcon }: ButtonContentProps) => { const ButtonContent = ({ children, icon, rightIcon }: ButtonContentProps) => {
const iconEl = icon ? ( const iconEl = icon ? icon : null;
<Icon name={icon} className="fill-current mr-2 align-text-top" /> const rightIconEl = rightIcon ? rightIcon : null;
) : null;
const rightIconEl = rightIcon ? (
<Icon name={rightIcon} className="fill-current ml-2 align-text-top" />
) : null;
return ( return (
<> <>
+53 -12
View File
@@ -29,7 +29,6 @@ import { countryCodeToFlagEmoji, FALLBACK_FLAG } from './flag-emoji';
// 🇿 // 🇿
const cases = [ const cases = [
['AC', '🇦🇨'],
['AD', '🇦🇩'], ['AD', '🇦🇩'],
['AE', '🇦🇪'], ['AE', '🇦🇪'],
['AF', '🇦🇫'], ['AF', '🇦🇫'],
@@ -38,11 +37,13 @@ const cases = [
['AL', '🇦🇱'], ['AL', '🇦🇱'],
['AM', '🇦🇲'], ['AM', '🇦🇲'],
['AO', '🇦🇴'], ['AO', '🇦🇴'],
['AQ', '🇦🇶'],
['AR', '🇦🇷'], ['AR', '🇦🇷'],
['AS', '🇦🇸'], ['AS', '🇦🇸'],
['AT', '🇦🇹'], ['AT', '🇦🇹'],
['AQ', '🇦🇶'], ['AU', '🇦🇺'],
['AW', '🇦🇼'], ['AW', '🇦🇼'],
['AX', '🇦🇽'],
['AZ', '🇦🇿'], ['AZ', '🇦🇿'],
['BA', '🇧🇦'], ['BA', '🇧🇦'],
['BB', '🇧🇧'], ['BB', '🇧🇧'],
@@ -57,10 +58,11 @@ const cases = [
['BM', '🇧🇲'], ['BM', '🇧🇲'],
['BN', '🇧🇳'], ['BN', '🇧🇳'],
['BO', '🇧🇴'], ['BO', '🇧🇴'],
['BQ', '🇧🇶'],
['BR', '🇧🇷'], ['BR', '🇧🇷'],
['BS', '🇧🇸'], ['BS', '🇧🇸'],
['BT', '🇧🇹'], ['BT', '🇧🇹'],
['BQ', '🇧🇶'], ['BV', '🇧🇻'],
['BW', '🇧🇼'], ['BW', '🇧🇼'],
['BY', '🇧🇾'], ['BY', '🇧🇾'],
['BZ', '🇧🇿'], ['BZ', '🇧🇿'],
@@ -76,19 +78,19 @@ const cases = [
['CM', '🇨🇲'], ['CM', '🇨🇲'],
['CN', '🇨🇳'], ['CN', '🇨🇳'],
['CO', '🇨🇴'], ['CO', '🇨🇴'],
['CP', '🇨🇵'],
['CR', '🇨🇷'], ['CR', '🇨🇷'],
['CU', '🇨🇺'],
['CV', '🇨🇻'],
['CW', '🇨🇼'], ['CW', '🇨🇼'],
['CX', '🇨🇽'],
['CY', '🇨🇾'], ['CY', '🇨🇾'],
['CZ', '🇨🇿'], ['CZ', '🇨🇿'],
['DE', '🇩🇪'], ['DE', '🇩🇪'],
['DG', '🇩🇬'],
['DJ', '🇩🇯'], ['DJ', '🇩🇯'],
['DK', '🇩🇰'], ['DK', '🇩🇰'],
['DM', '🇩🇲'], ['DM', '🇩🇲'],
['DO', '🇩🇴'], ['DO', '🇩🇴'],
['DZ', '🇩🇿'], ['DZ', '🇩🇿'],
['EA', '🇪🇦'],
['EC', '🇪🇨'], ['EC', '🇪🇨'],
['EE', '🇪🇪'], ['EE', '🇪🇪'],
['EG', '🇪🇬'], ['EG', '🇪🇬'],
@@ -114,10 +116,11 @@ const cases = [
['GM', '🇬🇲'], ['GM', '🇬🇲'],
['GN', '🇬🇳'], ['GN', '🇬🇳'],
['GP', '🇬🇵'], ['GP', '🇬🇵'],
['GQ', '🇬🇶'],
['GR', '🇬🇷'], ['GR', '🇬🇷'],
['GS', '🇬🇸'], ['GS', '🇬🇸'],
['GT', '🇬🇹'], ['GT', '🇬🇹'],
['GQ', '🇬🇶'], ['GU', '🇬🇺'],
['GW', '🇬🇼'], ['GW', '🇬🇼'],
['GY', '🇬🇾'], ['GY', '🇬🇾'],
['HK', '🇭🇰'], ['HK', '🇭🇰'],
@@ -125,17 +128,17 @@ const cases = [
['HN', '🇭🇳'], ['HN', '🇭🇳'],
['HR', '🇭🇷'], ['HR', '🇭🇷'],
['HT', '🇭🇹'], ['HT', '🇭🇹'],
['IC', '🇮🇨'], ['HU', '🇭🇺'],
['ID', '🇮🇩'], ['ID', '🇮🇩'],
['IE', '🇮🇪'], ['IE', '🇮🇪'],
['IL', '🇮🇱'], ['IL', '🇮🇱'],
['IM', '🇮🇲'], ['IM', '🇮🇲'],
['IN', '🇮🇳'], ['IN', '🇮🇳'],
['IO', '🇮🇴'], ['IO', '🇮🇴'],
['IQ', '🇮🇶'],
['IR', '🇮🇷'], ['IR', '🇮🇷'],
['IS', '🇮🇸'], ['IS', '🇮🇸'],
['IT', '🇮🇹'], ['IT', '🇮🇹'],
['IQ', '🇮🇶'],
['JE', '🇯🇪'], ['JE', '🇯🇪'],
['JM', '🇯🇲'], ['JM', '🇯🇲'],
['JO', '🇯🇴'], ['JO', '🇯🇴'],
@@ -159,6 +162,8 @@ const cases = [
['LR', '🇱🇷'], ['LR', '🇱🇷'],
['LS', '🇱🇸'], ['LS', '🇱🇸'],
['LT', '🇱🇹'], ['LT', '🇱🇹'],
['LU', '🇱🇺'],
['LV', '🇱🇻'],
['LY', '🇱🇾'], ['LY', '🇱🇾'],
['MA', '🇲🇦'], ['MA', '🇲🇦'],
['MC', '🇲🇨'], ['MC', '🇲🇨'],
@@ -173,11 +178,14 @@ const cases = [
['MN', '🇲🇳'], ['MN', '🇲🇳'],
['MO', '🇲🇴'], ['MO', '🇲🇴'],
['MP', '🇲🇵'], ['MP', '🇲🇵'],
['MQ', '🇲🇶'],
['MR', '🇲🇷'], ['MR', '🇲🇷'],
['MS', '🇲🇸'], ['MS', '🇲🇸'],
['MT', '🇲🇹'], ['MT', '🇲🇹'],
['MQ', '🇲🇶'], ['MU', '🇲🇺'],
['MV', '🇲🇻'],
['MW', '🇲🇼'], ['MW', '🇲🇼'],
['MX', '🇲🇽'],
['MY', '🇲🇾'], ['MY', '🇲🇾'],
['MZ', '🇲🇿'], ['MZ', '🇲🇿'],
['NA', '🇳🇦'], ['NA', '🇳🇦'],
@@ -190,6 +198,7 @@ const cases = [
['NO', '🇳🇴'], ['NO', '🇳🇴'],
['NP', '🇳🇵'], ['NP', '🇳🇵'],
['NR', '🇳🇷'], ['NR', '🇳🇷'],
['NU', '🇳🇺'],
['NZ', '🇳🇿'], ['NZ', '🇳🇿'],
['OM', '🇴🇲'], ['OM', '🇴🇲'],
['PA', '🇵🇦'], ['PA', '🇵🇦'],
@@ -206,9 +215,11 @@ const cases = [
['PT', '🇵🇹'], ['PT', '🇵🇹'],
['PW', '🇵🇼'], ['PW', '🇵🇼'],
['PY', '🇵🇾'], ['PY', '🇵🇾'],
['QA', '🇶🇦'],
['RE', '🇷🇪'], ['RE', '🇷🇪'],
['RO', '🇷🇴'], ['RO', '🇷🇴'],
['RS', '🇷🇸'], ['RS', '🇷🇸'],
['RU', '🇷🇺'],
['RW', '🇷🇼'], ['RW', '🇷🇼'],
['SA', '🇸🇦'], ['SA', '🇸🇦'],
['SB', '🇸🇧'], ['SB', '🇸🇧'],
@@ -227,9 +238,10 @@ const cases = [
['SR', '🇸🇷'], ['SR', '🇸🇷'],
['SS', '🇸🇸'], ['SS', '🇸🇸'],
['ST', '🇸🇹'], ['ST', '🇸🇹'],
['SV', '🇸🇻'],
['SX', '🇸🇽'],
['SY', '🇸🇾'], ['SY', '🇸🇾'],
['SZ', '🇸🇿'], ['SZ', '🇸🇿'],
['TA', '🇹🇦'],
['TC', '🇹🇨'], ['TC', '🇹🇨'],
['TD', '🇹🇩'], ['TD', '🇹🇩'],
['TF', '🇹🇫'], ['TF', '🇹🇫'],
@@ -243,10 +255,22 @@ const cases = [
['TO', '🇹🇴'], ['TO', '🇹🇴'],
['TR', '🇹🇷'], ['TR', '🇹🇷'],
['TT', '🇹🇹'], ['TT', '🇹🇹'],
['TV', '🇹🇻'],
['TW', '🇹🇼'], ['TW', '🇹🇼'],
['TZ', '🇹🇿'], ['TZ', '🇹🇿'],
['QA', '🇶🇦'], ['UA', '🇺🇦'],
['UG', '🇺🇬'],
['UM', '🇺🇲'],
['US', '🇺🇸'],
['UY', '🇺🇾'],
['UZ', '🇺🇿'],
['VA', '🇻🇦'],
['VC', '🇻🇨'],
['VE', '🇻🇪'],
['VG', '🇻🇬'], ['VG', '🇻🇬'],
['VI', '🇻🇮'],
['VN', '🇻🇳'],
['VU', '🇻🇺'],
['WF', '🇼🇫'], ['WF', '🇼🇫'],
['WS', '🇼🇸'], ['WS', '🇼🇸'],
['YE', '🇾🇪'], ['YE', '🇾🇪'],
@@ -254,6 +278,23 @@ const cases = [
['ZA', '🇿🇦'], ['ZA', '🇿🇦'],
['ZM', '🇿🇲'], ['ZM', '🇿🇲'],
['ZW', '🇿🇼'], ['ZW', '🇿🇼'],
// UK
['ENGLAND', '🏴󠁧󠁢󠁥󠁮󠁧󠁿'],
['GB-ENG', '🏴󠁧󠁢󠁥󠁮󠁧󠁿'],
['ENG', '🏴󠁧󠁢󠁥󠁮󠁧󠁿'],
['SCOTLAND', '🏴󠁧󠁢󠁳󠁣󠁴󠁿'],
['GB-SCT', '🏴󠁧󠁢󠁳󠁣󠁴󠁿'],
['SCT', '🏴󠁧󠁢󠁳󠁣󠁴󠁿'],
['WALES', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'],
['GB-WLS', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'],
['WLS', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'],
['CYMRU', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'],
['GB-CYM', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'],
['CYM', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'],
['NORTHERN IRELAND', '🇬🇧'],
['GB-NIR', '🇬🇧'],
['NIR', '🇬🇧'],
['UK', '🇬🇧'],
// unknown // unknown
['AA', FALLBACK_FLAG], ['AA', FALLBACK_FLAG],
['XX', FALLBACK_FLAG], ['XX', FALLBACK_FLAG],
+291 -11
View File
@@ -2,23 +2,303 @@ import compact from 'lodash/compact';
export const FALLBACK_FLAG = '🏳'; export const FALLBACK_FLAG = '🏳';
const KNOWN_CODES = `AC AD AE AF AG AI AL AM AO AR AS AT AQ AW AZ BA BB BD BE BF const KNOWN_CODES = [
BG BH BI BJ BL BM BN BO BR BS BT BQ BW BY BZ CA CC CD CF CG CH CI CK CL CM CN 'AD',
CO CP CR CW CY CZ DE DG DJ DK DM DO DZ EA EC EE EG EH ER ES ET FI FJ FK FM FO 'AE',
FR GA GB GD GE GF GG GH GI GL GM GN GP GR GS GT GQ GW GY HK HM HN HR HT IC ID 'AF',
IE IL IM IN IO IR IS IT IQ JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB 'AG',
LC LI LK LR LS LT LY MA MC MD ME MF MG MH MK ML MM MN MO MP MR MS MT MQ MW MY 'AI',
MZ NA NC NE NF NG NI NL NO NP NR NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW 'AL',
PY RE RO RS RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SY SZ TA TC 'AM',
TD TF TG TH TJ TK TL TM TN TO TR TT TW TZ QA VG WF WS YE YT ZA ZM ZW`; 'AO',
'AQ',
'AR',
'AS',
'AT',
'AU',
'AW',
'AX',
'AZ',
'BA',
'BB',
'BD',
'BE',
'BF',
'BG',
'BH',
'BI',
'BJ',
'BL',
'BM',
'BN',
'BO',
'BQ',
'BR',
'BS',
'BT',
'BV',
'BW',
'BY',
'BZ',
'CA',
'CC',
'CD',
'CF',
'CG',
'CH',
'CI',
'CK',
'CL',
'CM',
'CN',
'CO',
'CR',
'CU',
'CV',
'CW',
'CX',
'CY',
'CZ',
'DE',
'DJ',
'DK',
'DM',
'DO',
'DZ',
'EC',
'EE',
'EG',
'EH',
'ER',
'ES',
'ET',
'FI',
'FJ',
'FK',
'FM',
'FO',
'FR',
'GA',
'GB',
'GD',
'GE',
'GF',
'GG',
'GH',
'GI',
'GL',
'GM',
'GN',
'GP',
'GQ',
'GR',
'GS',
'GT',
'GU',
'GW',
'GY',
'HK',
'HM',
'HN',
'HR',
'HT',
'HU',
'ID',
'IE',
'IL',
'IM',
'IN',
'IO',
'IQ',
'IR',
'IS',
'IT',
'JE',
'JM',
'JO',
'JP',
'KE',
'KG',
'KH',
'KI',
'KM',
'KN',
'KP',
'KR',
'KW',
'KY',
'KZ',
'LA',
'LB',
'LC',
'LI',
'LK',
'LR',
'LS',
'LT',
'LU',
'LV',
'LY',
'MA',
'MC',
'MD',
'ME',
'MF',
'MG',
'MH',
'MK',
'ML',
'MM',
'MN',
'MO',
'MP',
'MQ',
'MR',
'MS',
'MT',
'MU',
'MV',
'MW',
'MX',
'MY',
'MZ',
'NA',
'NC',
'NE',
'NF',
'NG',
'NI',
'NL',
'NO',
'NP',
'NR',
'NU',
'NZ',
'OM',
'PA',
'PE',
'PF',
'PG',
'PH',
'PK',
'PL',
'PM',
'PN',
'PR',
'PS',
'PT',
'PW',
'PY',
'QA',
'RE',
'RO',
'RS',
'RU',
'RW',
'SA',
'SB',
'SC',
'SD',
'SE',
'SG',
'SH',
'SI',
'SJ',
'SK',
'SL',
'SM',
'SN',
'SO',
'SR',
'SS',
'ST',
'SV',
'SX',
'SY',
'SZ',
'TC',
'TD',
'TF',
'TG',
'TH',
'TJ',
'TK',
'TL',
'TM',
'TN',
'TO',
'TR',
'TT',
'TV',
'TW',
'TZ',
'UA',
'UG',
'UM',
'US',
'UY',
'UZ',
'VA',
'VC',
'VE',
'VG',
'VI',
'VN',
'VU',
'WF',
'WS',
'YE',
'YT',
'ZA',
'ZM',
'ZW',
];
export const countryCodeToFlagEmoji = (countryCode: string) => { const ENGLAND = '🏴󠁧󠁢󠁥󠁮󠁧󠁿';
const SCOTLAND = '🏴󠁧󠁢󠁳󠁣󠁴󠁿';
const WALES = '🏴󠁧󠁢󠁷󠁬󠁳󠁿';
const NORTHERN_IRELAND = '🇬🇧';
const UK = new Map([
['ENGLAND', ENGLAND],
['GB-ENG', ENGLAND],
['ENG', ENGLAND],
['SCOTLAND', SCOTLAND],
['GB-SCT', SCOTLAND],
['SCT', SCOTLAND],
['WALES', WALES],
['GB-WLS', WALES],
['WLS', WALES],
['CYMRU', WALES],
['GB-CYM', WALES],
['CYM', WALES],
['NORTHERN IRELAND', NORTHERN_IRELAND],
['GB-NIR', NORTHERN_IRELAND],
['NIR', NORTHERN_IRELAND],
['UNITED KINGDOM', '🇬🇧'],
['UK', '🇬🇧'],
]);
const EU = new Map([['EU', '🇪🇺']]);
const getCode = (countryCode: string): string => {
const code = countryCode.trim().toUpperCase(); const code = countryCode.trim().toUpperCase();
const known = compact(KNOWN_CODES.split(' ').map((ch) => ch.trim())); return code;
};
export const countryCodeToFlagEmoji = (countryCode: string): string => {
const code = getCode(countryCode);
const known = compact(KNOWN_CODES.map((ch) => ch.trim()));
if (known.includes(code)) { if (known.includes(code)) {
return code.replace(/./g, (char) => return code.replace(/./g, (char) =>
String.fromCodePoint(0x1f1a5 + char.toUpperCase().charCodeAt(0)) String.fromCodePoint(0x1f1a5 + char.toUpperCase().charCodeAt(0))
); );
} }
if (UK.has(code)) {
return UK.get(code) as string;
}
if (EU.has(code)) {
return EU.get(code) as string;
}
return FALLBACK_FLAG; return FALLBACK_FLAG;
}; };
@@ -98,6 +98,8 @@ interface ProposalNewMarketTerms {
decimalPlaces: string; decimalPlaces: string;
positionDecimalPlaces: string; positionDecimalPlaces: string;
lpPriceRange: string; lpPriceRange: string;
linearSlippageFactor: string;
quadraticSlippageFactor: string;
instrument: { instrument: {
name: string; name: string;
code: string; code: string;
@@ -130,6 +132,8 @@ interface ProposalUpdateMarketTerms {
updateMarket: { updateMarket: {
marketId: string; marketId: string;
changes: { changes: {
linearSlippageFactor: string;
quadraticSlippageFactor: string;
instrument: { instrument: {
code: string; code: string;
future: { future: {
@@ -1,6 +1,6 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Link } from '@vegaprotocol/ui-toolkit'; import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment'; import { EtherscanLink, useEnvironment } from '@vegaprotocol/environment';
import { EthTxStatus } from '../use-ethereum-transaction'; import { EthTxStatus } from '../use-ethereum-transaction';
const ACTIVE_CLASSES = 'text-black dark:text-white'; const ACTIVE_CLASSES = 'text-black dark:text-white';
@@ -62,14 +62,14 @@ export const TxRow = ({
}`} }`}
> >
<span>{t('Ethereum transaction complete')}</span> <span>{t('Ethereum transaction complete')}</span>
<Link {txHash && (
href={`${ETHERSCAN_URL}/tx/${txHash}`} <EtherscanLink
title={t('View on Etherscan')} tx={txHash}
className="text-vega-pink dark:text-vega-yellow" className="text-vega-pink dark:text-vega-yellow"
target="_blank" >
> {t('View transaction on Etherscan')}
{t('View transaction on Etherscan')} </EtherscanLink>
</Link> )}
</p> </p>
); );
} }
+11 -33
View File
@@ -9,14 +9,14 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers'; import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Link, ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
TypedDataAgGrid, TypedDataAgGrid,
VegaICellRendererParams, VegaICellRendererParams,
VegaValueFormatterParams, VegaValueFormatterParams,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { useEnvironment } from '@vegaprotocol/environment'; import { EtherscanLink } from '@vegaprotocol/environment';
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal'; import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3'; import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
@@ -27,7 +27,6 @@ export const WithdrawalsTable = (
props: TypedDataAgGrid<WithdrawalFieldsFragment> props: TypedDataAgGrid<WithdrawalFieldsFragment>
) => { ) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const { ETHERSCAN_URL } = useEnvironment();
const createWithdrawApproval = useEthWithdrawApprovalsStore( const createWithdrawApproval = useEthWithdrawApprovalsStore(
(store) => store.create (store) => store.create
); );
@@ -66,7 +65,6 @@ export const WithdrawalsTable = (
headerName={t('Recipient')} headerName={t('Recipient')}
field="details.receiverAddress" field="details.receiverAddress"
cellRenderer="RecipientCell" cellRenderer="RecipientCell"
cellRendererParams={{ ethUrl: ETHERSCAN_URL }}
valueFormatter={({ valueFormatter={({
value, value,
data, data,
@@ -126,7 +124,6 @@ export const WithdrawalsTable = (
complete: (withdrawal: WithdrawalFieldsFragment) => { complete: (withdrawal: WithdrawalFieldsFragment) => {
createWithdrawApproval(withdrawal); createWithdrawApproval(withdrawal);
}, },
ethUrl: ETHERSCAN_URL,
}} }}
cellRendererSelector={({ cellRendererSelector={({
data, data,
@@ -160,20 +157,12 @@ export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
export const EtherscanLinkCell = ({ export const EtherscanLinkCell = ({
value, value,
ethUrl, }: VegaValueFormatterParams<WithdrawalFieldsFragment, 'txHash'>) => {
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'txHash'> & {
ethUrl: string;
}) => {
if (!value) return '-'; if (!value) return '-';
return ( return (
<Link <EtherscanLink tx={value} data-testid="etherscan-link">
title={t('View transaction on Etherscan')}
href={`${ethUrl}/tx/${value}`}
data-testid="etherscan-link"
target="_blank"
>
{truncateByChars(value)} {truncateByChars(value)}
</Link> </EtherscanLink>
); );
}; };
@@ -193,28 +182,17 @@ export const StatusCell = ({ data }: { data: WithdrawalFieldsFragment }) => {
return <span>{t('Failed')}</span>; return <span>{t('Failed')}</span>;
}; };
export interface RecipientCellProps
extends VegaICellRendererParams<
WithdrawalFieldsFragment,
'details.receiverAddress'
> {
ethUrl: string;
}
const RecipientCell = ({ const RecipientCell = ({
ethUrl,
value, value,
valueFormatted, valueFormatted,
}: RecipientCellProps) => { }: VegaICellRendererParams<
WithdrawalFieldsFragment,
'details.receiverAddress'
>) => {
return ( return (
<Link <EtherscanLink address={value} data-testid="etherscan-link">
title={t('View on Etherscan (opens in a new tab)')}
href={`${ethUrl}/address/${value}`}
data-testid="etherscan-link"
target="_blank"
>
{valueFormatted} {valueFormatted}
</Link> </EtherscanLink>
); );
}; };