Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2880cade0e | ||
|
|
f5868b6f1b | ||
|
|
20e1522088 | ||
|
|
ae02b153f8 | ||
|
|
fee5f862a7 | ||
|
|
c25362b084 | ||
|
|
7c105d2c6b | ||
|
|
2dfcb02d1e | ||
|
|
a98ab775c3 | ||
|
|
097bf7c08c | ||
|
|
236e35a92b | ||
|
|
abf84b9d45 | ||
|
|
6d2a2ea0a0 | ||
|
|
6ac79e9f6d | ||
|
|
835ee64243 | ||
|
|
e7c3b5054c | ||
|
|
9dc9588a14 | ||
|
|
a0844d41bf | ||
|
|
3d1aa74128 | ||
|
|
2376bfe58f | ||
|
|
72ccf96523 | ||
|
|
f416fa4a25 | ||
|
|
a24e3646cc | ||
|
|
55b092b216 | ||
|
|
ef4cf6a87e | ||
|
|
c9860a3412 | ||
|
|
127bd3faf3 | ||
|
|
1791b3ca6a | ||
|
|
6044b86ff4 | ||
|
|
341d78e761 |
@@ -1,3 +1,2 @@
|
|||||||
* @vegaprotocol/frontend
|
* @vegaprotocol/frontend
|
||||||
* @vegaprotocol/frontend-qa
|
|
||||||
*.graphql @vegaprotocol/core
|
*.graphql @vegaprotocol/core
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AppFailure,
|
|
||||||
NetworkLoader,
|
NetworkLoader,
|
||||||
|
NodeFailure,
|
||||||
NodeGuard,
|
NodeGuard,
|
||||||
NodeSwitcherDialog,
|
NodeSwitcherDialog,
|
||||||
useEnvironment,
|
useEnvironment,
|
||||||
@@ -31,7 +31,7 @@ function App() {
|
|||||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||||
<NodeGuard
|
<NodeGuard
|
||||||
skeleton={<div>{t('Loading')}</div>}
|
skeleton={<div>{t('Loading')}</div>}
|
||||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||||
>
|
>
|
||||||
<Suspense fallback={splashLoading}>
|
<Suspense fallback={splashLoading}>
|
||||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export const ErrorBoundary = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GHOST = (
|
export const GHOST = (
|
||||||
<svg
|
<svg
|
||||||
width="56"
|
width="56"
|
||||||
height="85"
|
height="85"
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { BackgroundVideo } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const RestrictedPage = () => {
|
||||||
|
const errorTitle = '451 Unavailable';
|
||||||
|
const errorMessage =
|
||||||
|
'Due to uncertainty about the legal and regulatory status of the content hosted on this site, it is not available to visitors in your jurisdiction.';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BackgroundVideo className="brightness-50" />
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'max-w-[620px] p-2 mt-[10vh]',
|
||||||
|
'mx-auto my-0',
|
||||||
|
'antialiased text-white',
|
||||||
|
'overflow-hidden relative',
|
||||||
|
'flex flex-col gap-2'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div>{GHOST}</div>
|
||||||
|
<h1 className="text-[2.7rem] font-alpha calt break-words uppercase">
|
||||||
|
{errorTitle}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm mt-10 overflow-auto font-mono">
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GHOST = (
|
||||||
|
<svg
|
||||||
|
width="56"
|
||||||
|
height="85"
|
||||||
|
viewBox="0 0 150 234"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10.43 93.6821L0 104.097V172.435H5.13793V106.2L14.0779 97.3247L10.43 93.6821Z"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
<path d="M105.328 18.521H7.70703V172.435H105.328V18.521Z" fill="white" />
|
||||||
|
<path d="M38.5364 64.6953H33.3984V69.8258H38.5364V64.6953Z" fill="black" />
|
||||||
|
<path d="M43.6731 69.8257H38.5352V74.9561H43.6731V69.8257Z" fill="black" />
|
||||||
|
<path d="M48.8098 74.9561H43.6719V80.0865H48.8098V74.9561Z" fill="black" />
|
||||||
|
<path d="M38.5364 74.9561H33.3984V80.0865H38.5364V74.9561Z" fill="black" />
|
||||||
|
<path d="M48.8098 64.6953H43.6719V69.8258H48.8098V64.6953Z" fill="black" />
|
||||||
|
<path d="M69.3606 64.6953H64.2227V69.8258H69.3606V64.6953Z" fill="black" />
|
||||||
|
<path d="M74.5012 69.8257H69.3633V74.9561H74.5012V69.8257Z" fill="black" />
|
||||||
|
<path d="M79.6379 74.9561H74.5V80.0865H79.6379V74.9561Z" fill="black" />
|
||||||
|
<path d="M69.3606 74.9561H64.2227V80.0865H69.3606V74.9561Z" fill="black" />
|
||||||
|
<path d="M79.6379 64.6953H74.5V69.8258H79.6379V64.6953Z" fill="black" />
|
||||||
|
<path d="M79.6398 90.3477H33.3984V95.4781H79.6398V90.3477Z" fill="black" />
|
||||||
|
<path d="M48.8098 172.435H43.6719V234H48.8098V172.435Z" fill="black" />
|
||||||
|
<path d="M69.3606 172.435H64.2227V234H69.3606V172.435Z" fill="black" />
|
||||||
|
<path
|
||||||
|
d="M129.87 0.00408026L92.0273 14.3682L111.195 64.7203L149.038 50.3562L129.87 0.00408026Z"
|
||||||
|
fill="#FF0081"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M108.565 21.7836L103.762 23.6064L105.587 28.4024L110.39 26.5795L108.565 21.7836Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M115.189 24.7596L110.387 26.583L112.213 31.3785L117.015 29.5551L115.189 24.7596Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M121.826 27.7348L117.023 29.5576L118.849 34.3536L123.652 32.5307L121.826 27.7348Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M128.447 30.7059L123.645 32.5293L125.471 37.3247L130.273 35.5014L128.447 30.7059Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M135.084 33.6854L130.281 35.5083L132.107 40.3043L136.91 38.4814L135.084 33.6854Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M124.799 21.1156L119.996 22.939L121.822 27.7344L126.625 25.911L124.799 21.1156Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M127.775 14.492L122.973 16.3154L124.799 21.1109L129.601 19.2875L127.775 14.492Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M118.838 34.3499L114.035 36.1733L115.861 40.9688L120.664 39.1454L118.838 34.3499Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M115.857 40.9691L111.055 42.7925L112.881 47.5879L117.683 45.7645L115.857 40.9691Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M129.938 52.3822L139.906 91.5276L100.703 101.429L101.988 106.406L146.174 95.2215L134.922 51.0996L129.938 52.3822Z"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default RestrictedPage;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export const Routes = {
|
export const Routes = {
|
||||||
HOME: '/',
|
HOME: '/',
|
||||||
|
RESTRICTED: '/restricted',
|
||||||
TX: 'txs',
|
TX: 'txs',
|
||||||
BLOCKS: 'blocks',
|
BLOCKS: 'blocks',
|
||||||
PARTIES: 'parties',
|
PARTIES: 'parties',
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { remove0x } from '@vegaprotocol/utils';
|
|||||||
import { PartyAccountsByAsset } from './parties/id/accounts';
|
import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||||
import { Disclaimer } from './pages/disclaimer';
|
import { Disclaimer } from './pages/disclaimer';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
import RestrictedPage from './restricted';
|
||||||
|
|
||||||
export type Navigable = {
|
export type Navigable = {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -356,6 +357,14 @@ export const routerConfig: Route[] = [
|
|||||||
...validators,
|
...validators,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: Routes.RESTRICTED,
|
||||||
|
element: <RestrictedPage />,
|
||||||
|
handle: {
|
||||||
|
name: t('Restricted'),
|
||||||
|
text: t('Restricted'),
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const router = createBrowserRouter(routerConfig);
|
export const router = createBrowserRouter(routerConfig);
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ import {
|
|||||||
NetworkLoader,
|
NetworkLoader,
|
||||||
useInitializeEnv,
|
useInitializeEnv,
|
||||||
NodeGuard,
|
NodeGuard,
|
||||||
AppFailure,
|
|
||||||
NodeSwitcherDialog,
|
NodeSwitcherDialog,
|
||||||
useNodeSwitcherStore,
|
useNodeSwitcherStore,
|
||||||
DocsLinks,
|
DocsLinks,
|
||||||
|
NodeFailure,
|
||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { ENV } from './config';
|
import { ENV } from './config';
|
||||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||||
@@ -106,6 +106,7 @@ const Web3Container = ({
|
|||||||
useEthWithdrawApprovalsManager();
|
useEthWithdrawApprovalsManager();
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
|
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
|
||||||
store.connectors,
|
store.connectors,
|
||||||
store.initialize,
|
store.initialize,
|
||||||
@@ -184,7 +185,7 @@ const Web3Container = ({
|
|||||||
<TemplateSidebar sidebar={sideBar}>
|
<TemplateSidebar sidebar={sideBar}>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
</TemplateSidebar>
|
</TemplateSidebar>
|
||||||
<footer className="p-4 border-t border-neutral-700 break-all">
|
<footer className="p-4 break-all border-t border-neutral-700">
|
||||||
<NetworkInfo />
|
<NetworkInfo />
|
||||||
</footer>
|
</footer>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
@@ -239,6 +240,9 @@ const AppContainer = () => {
|
|||||||
store.setDialogOpen,
|
store.setDialogOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Hacky skip all the loading & web3 init for geo restricted users
|
||||||
|
const isRestricted = document?.location?.pathname?.includes('/restricted');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ENV.dsn && telemetryOn === 'true') {
|
if (ENV.dsn && telemetryOn === 'true') {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
@@ -304,6 +308,14 @@ const AppContainer = () => {
|
|||||||
}
|
}
|
||||||
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
|
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
|
||||||
|
|
||||||
|
if (isRestricted) {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<AppRouter />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
@@ -312,7 +324,7 @@ const AppContainer = () => {
|
|||||||
<NodeGuard
|
<NodeGuard
|
||||||
skeleton={<div>{t('Loading')}</div>}
|
skeleton={<div>{t('Loading')}</div>}
|
||||||
failure={
|
failure={
|
||||||
<AppFailure title={t('NodeUnsuitable', { url: VEGA_URL })} />
|
<NodeFailure title={t('NodeUnsuitable', { url: VEGA_URL })} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AsyncRenderer<EthereumConfig | null>
|
<AsyncRenderer<EthereumConfig | null>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"pageTitleRedemptionTranche": "Redeem from Tranche",
|
"pageTitleRedemptionTranche": "Redeem from Tranche",
|
||||||
"pageTitleTranches": "Vesting tranches",
|
"pageTitleTranches": "Vesting tranches",
|
||||||
"pageTitle404": "Page not found",
|
"pageTitle404": "Page not found",
|
||||||
|
"pageTitle451": "451 unavailable",
|
||||||
"pageTitleNotPermitted": "Can not proceed!",
|
"pageTitleNotPermitted": "Can not proceed!",
|
||||||
"pageTitleDisassociate": "Disassociate $VEGA tokens from a Vega key",
|
"pageTitleDisassociate": "Disassociate $VEGA tokens from a Vega key",
|
||||||
"pageTitleProposals": "Proposals",
|
"pageTitleProposals": "Proposals",
|
||||||
@@ -718,6 +719,8 @@
|
|||||||
"UpdateAssetProposal": "Update asset proposal",
|
"UpdateAssetProposal": "Update asset proposal",
|
||||||
"NewFreeformProposal": "New freeform proposal",
|
"NewFreeformProposal": "New freeform proposal",
|
||||||
"NewRawProposal": "New proposal",
|
"NewRawProposal": "New proposal",
|
||||||
|
"NewTransferProposal": "New transfer proposal",
|
||||||
|
"CancelTransferProposal": "Cancel transfer proposal",
|
||||||
"MARKET_STATE_UPDATE_TYPE_RESUME": "Resume market",
|
"MARKET_STATE_UPDATE_TYPE_RESUME": "Resume market",
|
||||||
"MARKET_STATE_UPDATE_TYPE_SUSPEND": "Suspend market",
|
"MARKET_STATE_UPDATE_TYPE_SUSPEND": "Suspend market",
|
||||||
"MARKET_STATE_UPDATE_TYPE_TERMINATE": "Terminate market",
|
"MARKET_STATE_UPDATE_TYPE_TERMINATE": "Terminate market",
|
||||||
@@ -738,6 +741,8 @@
|
|||||||
"UpdateVolumeDiscountProgram": "Update volume discount program",
|
"UpdateVolumeDiscountProgram": "Update volume discount program",
|
||||||
"NewAsset": "New asset",
|
"NewAsset": "New asset",
|
||||||
"UpdateAsset": "Update asset",
|
"UpdateAsset": "Update asset",
|
||||||
|
"NewTransfer": "New transfer",
|
||||||
|
"CancelTransfer": "Cancel transfer",
|
||||||
"AssetID": "Asset ID",
|
"AssetID": "Asset ID",
|
||||||
"Freeform": "Freeform",
|
"Freeform": "Freeform",
|
||||||
"RawProposal": "Let me choose (raw proposal)",
|
"RawProposal": "Let me choose (raw proposal)",
|
||||||
@@ -914,5 +919,31 @@
|
|||||||
"EndOfProgramTimestamp": "End of program",
|
"EndOfProgramTimestamp": "End of program",
|
||||||
"EndOfProgramTimestampDescription": "Time after which when the current epoch ends, the programs will end and benefits will be disabled.",
|
"EndOfProgramTimestampDescription": "Time after which when the current epoch ends, the programs will end and benefits will be disabled.",
|
||||||
"BenefitTierVolumeDiscountFactor": "Volume discount factor",
|
"BenefitTierVolumeDiscountFactor": "Volume discount factor",
|
||||||
"BenefitTierVolumeDiscountFactorDescription": "Discount given to those in this benefit tier"
|
"BenefitTierVolumeDiscountFactorDescription": "Discount given to those in this benefit tier",
|
||||||
|
"ACCOUNT_TYPE_INSURANCE": "Insurance account",
|
||||||
|
"ACCOUNT_TYPE_GLOBAL_INSURANCE": "Global insurance account",
|
||||||
|
"ACCOUNT_TYPE_SETTLEMENT": "Settlement account",
|
||||||
|
"ACCOUNT_TYPE_MARGIN": "Margin account ",
|
||||||
|
"ACCOUNT_TYPE_GENERAL": "General account",
|
||||||
|
"ACCOUNT_TYPE_FEES_INFRASTRUCTURE": "Infrastructure fees account",
|
||||||
|
"ACCOUNT_TYPE_FEES_LIQUIDITY": "Liquidity fees account",
|
||||||
|
"ACCOUNT_TYPE_FEES_MAKER": "Maker fees account",
|
||||||
|
"ACCOUNT_TYPE_BOND": "Bond account",
|
||||||
|
"ACCOUNT_TYPE_EXTERNAL": "External account",
|
||||||
|
"ACCOUNT_TYPE_GLOBAL_REWARD": "Global reward account",
|
||||||
|
"ACCOUNT_TYPE_PENDING_TRANSFERS": "Pending transfers account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES": "Maker paid fees reward account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES": "Maker received fees reward account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES": "Liquidity provider received fees reward account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS": "Market proposers reward account",
|
||||||
|
"ACCOUNT_TYPE_HOLDING": "Holding account",
|
||||||
|
"ACCOUNT_TYPE_LP_LIQUIDITY_FEES": "Liquidity provider fees account",
|
||||||
|
"ACCOUNT_TYPE_NETWORK_TREASURY": "Network treasury account",
|
||||||
|
"ACCOUNT_TYPE_VESTING_REWARDS": "Vesting rewards account",
|
||||||
|
"ACCOUNT_TYPE_VESTED_REWARDS": "Vested rewards account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_AVERAGE_POSITION": "Average position reward account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_RELATIVE_RETURN": "Relative return reward account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY": "Return volatility reward account",
|
||||||
|
"ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING": "Validator ranking reward account",
|
||||||
|
"ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD": "Pending fee referral reward account"
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-3
@@ -12,18 +12,19 @@ import {
|
|||||||
generateProposal,
|
generateProposal,
|
||||||
generateYesVotes,
|
generateYesVotes,
|
||||||
} from '../../test-helpers/generate-proposals';
|
} from '../../test-helpers/generate-proposals';
|
||||||
import { ProposalHeader } from './proposal-header';
|
import { ProposalHeader, NewTransferSummary } from './proposal-header';
|
||||||
import {
|
import {
|
||||||
lastWeek,
|
lastWeek,
|
||||||
nextWeek,
|
nextWeek,
|
||||||
mockWalletContext,
|
mockWalletContext,
|
||||||
createUserVoteQueryMock,
|
createUserVoteQueryMock,
|
||||||
} from '../../test-helpers/mocks';
|
} from '../../test-helpers/mocks';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
|
||||||
import type { MockedResponse } from '@apollo/client/testing';
|
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { VoteState } from '../vote-details/use-user-vote';
|
import { VoteState } from '../vote-details/use-user-vote';
|
||||||
|
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||||
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/proposals', () => ({
|
jest.mock('@vegaprotocol/proposals', () => ({
|
||||||
...jest.requireActual('@vegaprotocol/proposals'),
|
...jest.requireActual('@vegaprotocol/proposals'),
|
||||||
@@ -31,6 +32,7 @@ jest.mock('@vegaprotocol/proposals', () => ({
|
|||||||
code: 'PARENT_CODE',
|
code: 'PARENT_CODE',
|
||||||
parentMarketId: 'PARENT_ID',
|
parentMarketId: 'PARENT_ID',
|
||||||
}),
|
}),
|
||||||
|
useNewTransferProposalDetails: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const renderComponent = (
|
const renderComponent = (
|
||||||
@@ -418,3 +420,78 @@ describe('Proposal header', () => {
|
|||||||
expect(await screen.findByTestId('user-voted-yes')).toBeInTheDocument();
|
expect(await screen.findByTestId('user-voted-yes')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/proposals');
|
||||||
|
|
||||||
|
describe('<NewTransferSummary />', () => {
|
||||||
|
it('renders null if no details are provided', () => {
|
||||||
|
(useNewTransferProposalDetails as jest.Mock).mockReturnValue(null);
|
||||||
|
const { container } = render(<NewTransferSummary proposalId="1" />);
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles OneOffGovernanceTransfer', () => {
|
||||||
|
(useNewTransferProposalDetails as jest.Mock).mockReturnValue({
|
||||||
|
kind: { __typename: 'OneOffGovernanceTransfer', deliverOn: null },
|
||||||
|
source: '0x123',
|
||||||
|
sourceType: 'wallet',
|
||||||
|
destination: '0x456',
|
||||||
|
destinationType: 'contract',
|
||||||
|
});
|
||||||
|
const { getByText } = render(<NewTransferSummary proposalId="1" />);
|
||||||
|
const textMatch = (content: string) => content.includes('One off transfer');
|
||||||
|
expect(getByText(textMatch)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles RecurringGovernanceTransfer', () => {
|
||||||
|
(useNewTransferProposalDetails as jest.Mock).mockReturnValue({
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringGovernanceTransfer',
|
||||||
|
startEpoch: 1,
|
||||||
|
endEpoch: 5,
|
||||||
|
},
|
||||||
|
source: '0x123',
|
||||||
|
sourceType: 'wallet',
|
||||||
|
destination: '0x456',
|
||||||
|
destinationType: 'contract',
|
||||||
|
});
|
||||||
|
const { getByText } = render(<NewTransferSummary proposalId="1" />);
|
||||||
|
const textMatch = (content: string) =>
|
||||||
|
content.includes('Recurring transfer');
|
||||||
|
expect(getByText(textMatch)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to translated sourceType when source is not set', () => {
|
||||||
|
(useNewTransferProposalDetails as jest.Mock).mockReturnValue({
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringGovernanceTransfer',
|
||||||
|
startEpoch: 1,
|
||||||
|
endEpoch: 5,
|
||||||
|
},
|
||||||
|
source: undefined,
|
||||||
|
sourceType: 'ACCOUNT_TYPE_GENERAL',
|
||||||
|
destination: '0x456',
|
||||||
|
destinationType: 'ACCOUNT_TYPE_GENERAL',
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<NewTransferSummary proposalId="1" />);
|
||||||
|
expect(screen.getByText('General account')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to translated destinationType when destination is not set', () => {
|
||||||
|
(useNewTransferProposalDetails as jest.Mock).mockReturnValue({
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringGovernanceTransfer',
|
||||||
|
startEpoch: 1,
|
||||||
|
endEpoch: 5,
|
||||||
|
},
|
||||||
|
source: '0x123',
|
||||||
|
sourceType: 'ACCOUNT_TYPE_GENERAL',
|
||||||
|
destination: undefined,
|
||||||
|
destinationType: 'ACCOUNT_TYPE_GLOBAL_INSURANCE',
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<NewTransferSummary proposalId="1" />);
|
||||||
|
expect(screen.getByText('Global insurance account')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+23
-5
@@ -237,7 +237,11 @@ export const ProposalHeader = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
|
export const SuccessorCode = ({
|
||||||
|
proposalId,
|
||||||
|
}: {
|
||||||
|
proposalId?: string | null;
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const successor = useSuccessorMarketProposalDetails(proposalId);
|
const successor = useSuccessorMarketProposalDetails(proposalId);
|
||||||
|
|
||||||
@@ -254,7 +258,11 @@ const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
|
|||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NewTransferSummary = ({ proposalId }: { proposalId?: string | null }) => {
|
export const NewTransferSummary = ({
|
||||||
|
proposalId,
|
||||||
|
}: {
|
||||||
|
proposalId?: string | null;
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const details = useNewTransferProposalDetails(proposalId);
|
const details = useNewTransferProposalDetails(proposalId);
|
||||||
|
|
||||||
@@ -263,13 +271,23 @@ const NewTransferSummary = ({ proposalId }: { proposalId?: string | null }) => {
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{GovernanceTransferKindMapping[details.kind.__typename]}{' '}
|
{GovernanceTransferKindMapping[details.kind.__typename]}{' '}
|
||||||
{t('transfer from')} <Lozenge>{truncateMiddle(details.source)}</Lozenge>{' '}
|
{t('transfer from')}{' '}
|
||||||
{t('to')} <Lozenge>{truncateMiddle(details.destination)}</Lozenge>
|
<Lozenge>
|
||||||
|
{details.source
|
||||||
|
? truncateMiddle(details.source)
|
||||||
|
: t(details.sourceType)}
|
||||||
|
</Lozenge>{' '}
|
||||||
|
{t('to')}{' '}
|
||||||
|
<Lozenge>
|
||||||
|
{details.destination
|
||||||
|
? truncateMiddle(details.destination)
|
||||||
|
: t(details.destinationType)}
|
||||||
|
</Lozenge>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CancelTransferSummary = ({
|
export const CancelTransferSummary = ({
|
||||||
proposalId,
|
proposalId,
|
||||||
}: {
|
}: {
|
||||||
proposalId?: string | null;
|
proposalId?: string | null;
|
||||||
|
|||||||
+36
-39
@@ -63,44 +63,42 @@ const mockReferralProposal = generateProposal({
|
|||||||
terms: {
|
terms: {
|
||||||
change: {
|
change: {
|
||||||
__typename: 'UpdateReferralProgram',
|
__typename: 'UpdateReferralProgram',
|
||||||
changes: {
|
benefitTiers: [
|
||||||
benefitTiers: [
|
{
|
||||||
{
|
minimumEpochs: 6,
|
||||||
minimumEpochs: 6,
|
minimumRunningNotionalTakerVolume: '10000',
|
||||||
minimumRunningNotionalTakerVolume: '10000',
|
referralDiscountFactor: '0.001',
|
||||||
referralDiscountFactor: '0.001',
|
referralRewardFactor: '0.001',
|
||||||
referralRewardFactor: '0.001',
|
},
|
||||||
},
|
{
|
||||||
{
|
minimumEpochs: 24,
|
||||||
minimumEpochs: 24,
|
minimumRunningNotionalTakerVolume: '500000',
|
||||||
minimumRunningNotionalTakerVolume: '500000',
|
referralDiscountFactor: '0.005',
|
||||||
referralDiscountFactor: '0.005',
|
referralRewardFactor: '0.005',
|
||||||
referralRewardFactor: '0.005',
|
},
|
||||||
},
|
{
|
||||||
{
|
minimumEpochs: 48,
|
||||||
minimumEpochs: 48,
|
minimumRunningNotionalTakerVolume: '1000000',
|
||||||
minimumRunningNotionalTakerVolume: '1000000',
|
referralDiscountFactor: '0.01',
|
||||||
referralDiscountFactor: '0.01',
|
referralRewardFactor: '0.01',
|
||||||
referralRewardFactor: '0.01',
|
},
|
||||||
},
|
],
|
||||||
],
|
endOfProgram: '2026-10-03T10:34:34Z',
|
||||||
endOfProgramTimestamp: '2026-10-03T10:34:34Z',
|
windowLength: 3,
|
||||||
windowLength: 3,
|
stakingTiers: [
|
||||||
stakingTiers: [
|
{
|
||||||
{
|
minimumStakedTokens: '1',
|
||||||
minimumStakedTokens: '1',
|
referralRewardMultiplier: '1',
|
||||||
referralRewardMultiplier: '1',
|
},
|
||||||
},
|
{
|
||||||
{
|
minimumStakedTokens: '2',
|
||||||
minimumStakedTokens: '2',
|
referralRewardMultiplier: '2',
|
||||||
referralRewardMultiplier: '2',
|
},
|
||||||
},
|
{
|
||||||
{
|
minimumStakedTokens: '5',
|
||||||
minimumStakedTokens: '5',
|
referralRewardMultiplier: '3',
|
||||||
referralRewardMultiplier: '3',
|
},
|
||||||
},
|
],
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -132,7 +130,6 @@ describe('<ProposalReferralProgramDetails />', () => {
|
|||||||
terms: {
|
terms: {
|
||||||
change: {
|
change: {
|
||||||
__typename: 'UpdateReferralProgram',
|
__typename: 'UpdateReferralProgram',
|
||||||
changes: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-5
@@ -54,11 +54,10 @@ export const ProposalReferralProgramDetails = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const benefitTiers = proposal?.terms?.change?.changes?.benefitTiers;
|
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||||
const stakingTiers = proposal?.terms?.change?.changes?.stakingTiers;
|
const stakingTiers = proposal?.terms?.change?.stakingTiers;
|
||||||
const windowLength = proposal?.terms?.change?.changes?.windowLength;
|
const windowLength = proposal?.terms?.change?.windowLength;
|
||||||
const endOfProgramTimestamp =
|
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
|
||||||
proposal?.terms?.change?.changes?.endOfProgramTimestamp;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!benefitTiers &&
|
!benefitTiers &&
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export const useProposalNetworkParams = ({
|
|||||||
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
||||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
||||||
|
NetworkParams.governance_proposal_transfer_requiredParticipation,
|
||||||
|
NetworkParams.governance_proposal_transfer_requiredMajority,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fallback = {
|
const fallback = {
|
||||||
@@ -111,6 +113,14 @@ export const useProposalNetworkParams = ({
|
|||||||
params.governance_proposal_VolumeDiscountProgram_requiredParticipation
|
params.governance_proposal_VolumeDiscountProgram_requiredParticipation
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
case 'NewTransfer':
|
||||||
|
case 'CancelTransfer':
|
||||||
|
return {
|
||||||
|
requiredMajority: params.governance_proposal_transfer_requiredMajority,
|
||||||
|
requiredParticipation: new BigNumber(
|
||||||
|
params.governance_proposal_transfer_requiredParticipation
|
||||||
|
),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,19 +47,17 @@ fragment UpdateReferralProgram on Proposal {
|
|||||||
terms {
|
terms {
|
||||||
change {
|
change {
|
||||||
... on UpdateReferralProgram {
|
... on UpdateReferralProgram {
|
||||||
changes {
|
benefitTiers {
|
||||||
benefitTiers {
|
minimumEpochs
|
||||||
minimumEpochs
|
minimumRunningNotionalTakerVolume
|
||||||
minimumRunningNotionalTakerVolume
|
referralDiscountFactor
|
||||||
referralDiscountFactor
|
referralRewardFactor
|
||||||
referralRewardFactor
|
}
|
||||||
}
|
endOfProgram: endOfProgramTimestamp
|
||||||
endOfProgramTimestamp
|
windowLength
|
||||||
windowLength
|
stakingTiers {
|
||||||
stakingTiers {
|
minimumStakedTokens
|
||||||
minimumStakedTokens
|
referralRewardMultiplier
|
||||||
referralRewardMultiplier
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -47,19 +47,17 @@ fragment UpdateReferralPrograms on Proposal {
|
|||||||
terms {
|
terms {
|
||||||
change {
|
change {
|
||||||
... on UpdateReferralProgram {
|
... on UpdateReferralProgram {
|
||||||
changes {
|
benefitTiers {
|
||||||
benefitTiers {
|
minimumEpochs
|
||||||
minimumEpochs
|
minimumRunningNotionalTakerVolume
|
||||||
minimumRunningNotionalTakerVolume
|
referralDiscountFactor
|
||||||
referralDiscountFactor
|
referralRewardFactor
|
||||||
referralRewardFactor
|
}
|
||||||
}
|
endOfProgram: endOfProgramTimestamp
|
||||||
endOfProgramTimestamp
|
windowLength
|
||||||
windowLength
|
stakingTiers {
|
||||||
stakingTiers {
|
minimumStakedTokens
|
||||||
minimumStakedTokens
|
referralRewardMultiplier
|
||||||
referralRewardMultiplier
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-15
@@ -7,7 +7,7 @@ export type NewMarketProductFieldsFragment = { __typename?: 'Proposal', terms: {
|
|||||||
|
|
||||||
export type UpdateMarketStatesFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
export type UpdateMarketStatesFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||||
|
|
||||||
export type UpdateReferralProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram', changes: { __typename?: 'ReferralProgram', endOfProgramTimestamp: string, windowLength: number, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
export type UpdateReferralProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram', windowLength: number, endOfProgram: string, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||||
|
|
||||||
export type UpdateVolumeDiscountProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } } };
|
export type UpdateVolumeDiscountProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } } };
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export type ProposalsQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram', changes: { __typename?: 'ReferralProgram', endOfProgramTimestamp: string, windowLength: number, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
|
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram', windowLength: number, endOfProgram: string, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
|
||||||
|
|
||||||
export const NewMarketProductFieldsFragmentDoc = gql`
|
export const NewMarketProductFieldsFragmentDoc = gql`
|
||||||
fragment NewMarketProductFields on Proposal {
|
fragment NewMarketProductFields on Proposal {
|
||||||
@@ -75,19 +75,17 @@ export const UpdateReferralProgramsFragmentDoc = gql`
|
|||||||
terms {
|
terms {
|
||||||
change {
|
change {
|
||||||
... on UpdateReferralProgram {
|
... on UpdateReferralProgram {
|
||||||
changes {
|
benefitTiers {
|
||||||
benefitTiers {
|
minimumEpochs
|
||||||
minimumEpochs
|
minimumRunningNotionalTakerVolume
|
||||||
minimumRunningNotionalTakerVolume
|
referralDiscountFactor
|
||||||
referralDiscountFactor
|
referralRewardFactor
|
||||||
referralRewardFactor
|
}
|
||||||
}
|
endOfProgram: endOfProgramTimestamp
|
||||||
endOfProgramTimestamp
|
windowLength
|
||||||
windowLength
|
stakingTiers {
|
||||||
stakingTiers {
|
minimumStakedTokens
|
||||||
minimumStakedTokens
|
referralRewardMultiplier
|
||||||
referralRewardMultiplier
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||||
|
import type { RouteChildProps } from '..';
|
||||||
|
import { BackgroundVideo } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const Restricted = ({ name }: RouteChildProps) => {
|
||||||
|
useDocumentTitle(name);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const errorMessage =
|
||||||
|
'Due to uncertainty about the legal and regulatory status of the content hosted on this site, it is not available to visitors in your jurisdiction.';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BackgroundVideo className="brightness-50" />
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'max-w-[620px] p-2 mt-[10vh]',
|
||||||
|
'mx-auto my-0',
|
||||||
|
'antialiased text-white',
|
||||||
|
'overflow-hidden relative',
|
||||||
|
'flex flex-col gap-2'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div>{GHOST}</div>
|
||||||
|
<h1 className="leading-relaxed mb-0 text-[2.7rem] font-alpha calt break-words uppercase">
|
||||||
|
{t('pageTitle451')}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm mt-10 overflow-auto font-mono">
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GHOST = (
|
||||||
|
<svg
|
||||||
|
width="56"
|
||||||
|
height="85"
|
||||||
|
viewBox="0 0 150 234"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10.43 93.6821L0 104.097V172.435H5.13793V106.2L14.0779 97.3247L10.43 93.6821Z"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
<path d="M105.328 18.521H7.70703V172.435H105.328V18.521Z" fill="white" />
|
||||||
|
<path d="M38.5364 64.6953H33.3984V69.8258H38.5364V64.6953Z" fill="black" />
|
||||||
|
<path d="M43.6731 69.8257H38.5352V74.9561H43.6731V69.8257Z" fill="black" />
|
||||||
|
<path d="M48.8098 74.9561H43.6719V80.0865H48.8098V74.9561Z" fill="black" />
|
||||||
|
<path d="M38.5364 74.9561H33.3984V80.0865H38.5364V74.9561Z" fill="black" />
|
||||||
|
<path d="M48.8098 64.6953H43.6719V69.8258H48.8098V64.6953Z" fill="black" />
|
||||||
|
<path d="M69.3606 64.6953H64.2227V69.8258H69.3606V64.6953Z" fill="black" />
|
||||||
|
<path d="M74.5012 69.8257H69.3633V74.9561H74.5012V69.8257Z" fill="black" />
|
||||||
|
<path d="M79.6379 74.9561H74.5V80.0865H79.6379V74.9561Z" fill="black" />
|
||||||
|
<path d="M69.3606 74.9561H64.2227V80.0865H69.3606V74.9561Z" fill="black" />
|
||||||
|
<path d="M79.6379 64.6953H74.5V69.8258H79.6379V64.6953Z" fill="black" />
|
||||||
|
<path d="M79.6398 90.3477H33.3984V95.4781H79.6398V90.3477Z" fill="black" />
|
||||||
|
<path d="M48.8098 172.435H43.6719V234H48.8098V172.435Z" fill="black" />
|
||||||
|
<path d="M69.3606 172.435H64.2227V234H69.3606V172.435Z" fill="black" />
|
||||||
|
<path
|
||||||
|
d="M129.87 0.00408026L92.0273 14.3682L111.195 64.7203L149.038 50.3562L129.87 0.00408026Z"
|
||||||
|
fill="#FF0081"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M108.565 21.7836L103.762 23.6064L105.587 28.4024L110.39 26.5795L108.565 21.7836Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M115.189 24.7596L110.387 26.583L112.213 31.3785L117.015 29.5551L115.189 24.7596Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M121.826 27.7348L117.023 29.5576L118.849 34.3536L123.652 32.5307L121.826 27.7348Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M128.447 30.7059L123.645 32.5293L125.471 37.3247L130.273 35.5014L128.447 30.7059Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M135.084 33.6854L130.281 35.5083L132.107 40.3043L136.91 38.4814L135.084 33.6854Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M124.799 21.1156L119.996 22.939L121.822 27.7344L126.625 25.911L124.799 21.1156Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M127.775 14.492L122.973 16.3154L124.799 21.1109L129.601 19.2875L127.775 14.492Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M118.838 34.3499L114.035 36.1733L115.861 40.9688L120.664 39.1454L118.838 34.3499Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M115.857 40.9691L111.055 42.7925L112.881 47.5879L117.683 45.7645L115.857 40.9691Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M129.938 52.3822L139.906 91.5276L100.703 101.429L101.988 106.406L146.174 95.2215L134.922 51.0996L129.938 52.3822Z"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Restricted;
|
||||||
@@ -4,6 +4,7 @@ import Home from './token';
|
|||||||
import NotFound from './not-found';
|
import NotFound from './not-found';
|
||||||
import NotPermitted from './not-permitted';
|
import NotPermitted from './not-permitted';
|
||||||
import Routes from './routes';
|
import Routes from './routes';
|
||||||
|
import Restricted from './restricted';
|
||||||
|
|
||||||
const LazyTranches = React.lazy(
|
const LazyTranches = React.lazy(
|
||||||
() =>
|
() =>
|
||||||
@@ -361,8 +362,13 @@ const routerConfig = [
|
|||||||
element: <LazyDisclaimer name="Disclaimer" />,
|
element: <LazyDisclaimer name="Disclaimer" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: Routes.RESTRICTED,
|
||||||
// Not lazy as loaded when a user first hits the site
|
// Not lazy as loaded when a user first hits the site
|
||||||
|
element: <Restricted name="451 Unavailable" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
// Also not lazy as loaded when a user first hits the site
|
||||||
element: <NotFound name="NotFound" />,
|
element: <NotFound name="NotFound" />,
|
||||||
},
|
},
|
||||||
...redirects,
|
...redirects,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const Routes = {
|
|||||||
PROPOSALS_REJECTED: '/proposals/rejected',
|
PROPOSALS_REJECTED: '/proposals/rejected',
|
||||||
PROTOCOL_UPGRADES: '/protocol-upgrades',
|
PROTOCOL_UPGRADES: '/protocol-upgrades',
|
||||||
NOT_PERMITTED: '/not-permitted',
|
NOT_PERMITTED: '/not-permitted',
|
||||||
|
RESTRICTED: '/restricted',
|
||||||
NOT_FOUND: '/not-found',
|
NOT_FOUND: '/not-found',
|
||||||
CONTRACTS: '/contracts',
|
CONTRACTS: '/contracts',
|
||||||
TOKEN: '/token',
|
TOKEN: '/token',
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
.pre-loader {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
.pre-loader .pre-loader-center {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pre-loader .pre-loader-wrapper {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(0) {
|
|
||||||
animation-delay: 0ms;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:first-child {
|
|
||||||
animation-delay: -0.1s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(2) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(3) {
|
|
||||||
animation-delay: -0.45s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(4) {
|
|
||||||
animation-delay: 1s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(5) {
|
|
||||||
animation-delay: -0.75s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(6) {
|
|
||||||
animation-delay: 0.9s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(7) {
|
|
||||||
animation-delay: -1.4s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(8) {
|
|
||||||
animation-delay: 1.6s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(9) {
|
|
||||||
animation-delay: -0.45s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(10) {
|
|
||||||
animation-delay: 1.5s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(11) {
|
|
||||||
animation-delay: -2.75s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(12) {
|
|
||||||
animation-delay: 1.2s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(13) {
|
|
||||||
animation-delay: -1.95s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(14) {
|
|
||||||
animation-delay: 2.8s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(15) {
|
|
||||||
animation-delay: -0.75s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(16) {
|
|
||||||
animation-delay: 4s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(17) {
|
|
||||||
animation-delay: -0.85s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(18) {
|
|
||||||
animation-delay: 1.8s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(19) {
|
|
||||||
animation-delay: -1.9s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(20) {
|
|
||||||
animation-delay: 5s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(21) {
|
|
||||||
animation-delay: -5.25s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(22) {
|
|
||||||
animation-delay: 4.4s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(23) {
|
|
||||||
animation-delay: -5.75s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(24) {
|
|
||||||
animation-delay: 4.8s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(25) {
|
|
||||||
animation-delay: -5s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item {
|
|
||||||
animation: flickering 0.4s linear infinite alternate;
|
|
||||||
}
|
|
||||||
@keyframes flickering {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
26% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html.dark .pre-loader .loader-item {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
.pre-loader {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
.loader-item {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
.pre-loader-center {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pre-loader-wrapper {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
@for $i from 0 through 25 {
|
|
||||||
.loader-item:nth-child(#{$i}) {
|
|
||||||
@if $i % 2 == 0 {
|
|
||||||
animation-delay: #{$i * 50 * random(5)}ms;
|
|
||||||
animation-direction: reverse;
|
|
||||||
} @else {
|
|
||||||
animation-delay: #{$i * -50 * random(5)}ms;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.loader-item {
|
|
||||||
animation: flickering 0.4s linear alternate infinite;
|
|
||||||
}
|
|
||||||
@keyframes flickering {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
26% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html.dark {
|
|
||||||
.pre-loader {
|
|
||||||
.loader-item {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import { removeDecimal } from '@vegaprotocol/cypress';
|
|
||||||
import { ethers } from 'ethers';
|
|
||||||
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
|
||||||
import { selectAsset } from '../support/helpers';
|
|
||||||
|
|
||||||
const assetSelectField = 'select[name="asset"]';
|
|
||||||
const toAddressField = 'input[name="to"]';
|
|
||||||
const amountField = 'input[name="amount"]';
|
|
||||||
const formFieldError = 'input-error-text';
|
|
||||||
|
|
||||||
const ASSET_EURO = 1;
|
|
||||||
|
|
||||||
describe('deposit form validation', { tags: '@smoke' }, () => {
|
|
||||||
function openDepositForm() {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
|
||||||
cy.getByTestId('Deposits').click();
|
|
||||||
cy.getByTestId('deposit-button').click();
|
|
||||||
connectEthereumWallet('MetaMask');
|
|
||||||
cy.wait('@Assets');
|
|
||||||
}
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
openDepositForm();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles empty fields', () => {
|
|
||||||
cy.getByTestId('deposit-submit').click();
|
|
||||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
|
||||||
// once Ethereum wallet is connected and key selected the only field that will
|
|
||||||
// error is the asset select
|
|
||||||
cy.getByTestId(formFieldError).should('have.length', 1);
|
|
||||||
cy.get('[data-testid="input-error-text"][aria-describedby="asset"]').should(
|
|
||||||
'have.length',
|
|
||||||
1
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unable to select assets not enabled', () => {
|
|
||||||
// Assets not enabled in mocks
|
|
||||||
cy.get(assetSelectField + ' option:contains(Asset 2)').should('not.exist');
|
|
||||||
cy.get(assetSelectField + ' option:contains(Asset 3)').should('not.exist');
|
|
||||||
cy.get(assetSelectField + ' option:contains(Asset 4)').should('not.exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid public key when entering address manually', () => {
|
|
||||||
cy.getByTestId('enter-pubkey-manually').click();
|
|
||||||
cy.get(toAddressField).clear().type('INVALID_DEPOSIT_TO_ADDRESS');
|
|
||||||
cy.get(`[data-testid="${formFieldError}"][aria-describedby="to"]`).should(
|
|
||||||
'have.text',
|
|
||||||
'Invalid Vega key'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid amount', () => {
|
|
||||||
mockWeb3DepositCalls({
|
|
||||||
allowance: '1000',
|
|
||||||
depositLifetimeLimit: '1000',
|
|
||||||
balance: '800',
|
|
||||||
deposited: '0',
|
|
||||||
dps: 5,
|
|
||||||
});
|
|
||||||
// Deposit amount smaller than minimum viable for selected asset
|
|
||||||
// Select an amount so that we have a known decimal places value to work with
|
|
||||||
selectAsset(ASSET_EURO);
|
|
||||||
|
|
||||||
cy.get(amountField)
|
|
||||||
.clear()
|
|
||||||
.type('0.00000000000000000000000000000000001')
|
|
||||||
.next(`[data-testid="${formFieldError}"]`)
|
|
||||||
.should('have.text', 'Value is below minimum');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('insufficient funds', () => {
|
|
||||||
// 1001-DEPO-004
|
|
||||||
mockWeb3DepositCalls({
|
|
||||||
allowance: '1000',
|
|
||||||
depositLifetimeLimit: '1000',
|
|
||||||
balance: '800',
|
|
||||||
deposited: '0',
|
|
||||||
dps: 5,
|
|
||||||
});
|
|
||||||
cy.get(amountField)
|
|
||||||
.clear()
|
|
||||||
.type('850')
|
|
||||||
.next(`[data-testid="${formFieldError}"]`)
|
|
||||||
.should(
|
|
||||||
'have.text',
|
|
||||||
"You can't deposit more than you have in your Ethereum wallet, 800 tEURO"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deposit actions', { tags: '@smoke' }, () => {
|
|
||||||
before(() => {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/markets/market-1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('Deposit to trade is visible', () => {
|
|
||||||
cy.getByTestId('Collateral').click();
|
|
||||||
cy.get('[row-id="asset-id"]').contains('tEURO').should('be.visible');
|
|
||||||
cy.contains('[data-testid="deposit"]', 'Deposit').should('be.visible');
|
|
||||||
cy.contains('[data-testid="deposit"]', 'Deposit').click();
|
|
||||||
cy.getByTestId('deposit-submit').should('be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function mockWeb3DepositCalls({
|
|
||||||
allowance,
|
|
||||||
depositLifetimeLimit,
|
|
||||||
balance,
|
|
||||||
deposited,
|
|
||||||
dps,
|
|
||||||
}: {
|
|
||||||
allowance: string;
|
|
||||||
depositLifetimeLimit: string;
|
|
||||||
balance: string;
|
|
||||||
deposited: string;
|
|
||||||
dps: number;
|
|
||||||
}) {
|
|
||||||
const assetContractAddress = '0x0158031158bb4df2ad02eaa31e8963e84ea978a4';
|
|
||||||
const collateralBridgeAddress = '0x7fe27d970bc8afc3b11cc8d9737bfb66b1efd799';
|
|
||||||
const toResult = (value: string, dps: number) => {
|
|
||||||
const rawValue = removeDecimal(value, dps);
|
|
||||||
return ethers.utils.hexZeroPad(
|
|
||||||
ethers.utils.hexlify(parseInt(rawValue)),
|
|
||||||
32
|
|
||||||
);
|
|
||||||
};
|
|
||||||
cy.intercept('POST', 'http://localhost:8545', (req) => {
|
|
||||||
// Mock chainId call
|
|
||||||
if (req.body.method === 'eth_chainId') {
|
|
||||||
req.alias = 'eth_chainId';
|
|
||||||
req.reply({
|
|
||||||
id: req.body.id,
|
|
||||||
jsonrpc: req.body.jsonrpc,
|
|
||||||
result: '0xaa36a7', // 11155111 for sepolia chain id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock deposited amount
|
|
||||||
if (req.body.method === 'eth_getStorageAt') {
|
|
||||||
req.alias = 'eth_getStorageAt';
|
|
||||||
req.reply({
|
|
||||||
id: req.body.id,
|
|
||||||
jsonrpc: req.body.jsonrpc,
|
|
||||||
result: toResult(deposited, dps),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.body.method === 'eth_call') {
|
|
||||||
// Mock approved amount for asset on collateral bridge
|
|
||||||
if (
|
|
||||||
req.body.params[0].to === assetContractAddress &&
|
|
||||||
req.body.params[0].data ===
|
|
||||||
'0xdd62ed3e000000000000000000000000ee7d375bcb50c26d52e1a4a472d8822a2a22d94f0000000000000000000000007fe27d970bc8afc3b11cc8d9737bfb66b1efd799'
|
|
||||||
) {
|
|
||||||
req.alias = 'eth_call_allowance';
|
|
||||||
req.reply({
|
|
||||||
id: req.body.id,
|
|
||||||
jsonrpc: req.body.jsonrpc,
|
|
||||||
result: toResult(allowance, dps),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Mock balance of asset in Ethereum wallet
|
|
||||||
else if (
|
|
||||||
req.body.params[0].to === assetContractAddress &&
|
|
||||||
req.body.params[0].data ===
|
|
||||||
'0x70a08231000000000000000000000000ee7d375bcb50c26d52e1a4a472d8822a2a22d94f'
|
|
||||||
) {
|
|
||||||
req.alias = 'eth_call_balanceOf';
|
|
||||||
req.reply({
|
|
||||||
id: req.body.id,
|
|
||||||
jsonrpc: req.body.jsonrpc,
|
|
||||||
result: toResult(balance, dps),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Mock deposit lifetime limit
|
|
||||||
else if (
|
|
||||||
req.body.params[0].to === collateralBridgeAddress &&
|
|
||||||
req.body.params[0].data ===
|
|
||||||
'0x354a897a0000000000000000000000000158031158bb4df2ad02eaa31e8963e84ea978a4'
|
|
||||||
) {
|
|
||||||
req.alias = 'eth_call_get_deposit_maximum'; // deposit lifetime limit
|
|
||||||
req.reply({
|
|
||||||
id: req.body.id,
|
|
||||||
jsonrpc: req.body.jsonrpc,
|
|
||||||
result: toResult(depositLifetimeLimit, dps),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,529 +0,0 @@
|
|||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
import type { OrderAmendment, OrderCancellation } from '@vegaprotocol/wallet';
|
|
||||||
import {
|
|
||||||
updateOrder,
|
|
||||||
getSubscriptionMocks,
|
|
||||||
} from '../support/order-update-subscription';
|
|
||||||
import {
|
|
||||||
testOrderCancellation,
|
|
||||||
testOrderAmendment,
|
|
||||||
} from '../support/order-validation';
|
|
||||||
|
|
||||||
const orderSymbol = 'instrument-code';
|
|
||||||
const orderSize = 'size';
|
|
||||||
const orderType = 'type';
|
|
||||||
const orderStatus = 'status';
|
|
||||||
const orderRemaining = 'remaining';
|
|
||||||
const orderPrice = 'price';
|
|
||||||
const orderTimeInForce = 'timeInForce';
|
|
||||||
const orderUpdatedAt = 'updatedAt';
|
|
||||||
const cancelOrderBtn = 'cancel';
|
|
||||||
const cancelAllOrdersBtn = 'cancelAll';
|
|
||||||
const editOrderBtn = 'edit';
|
|
||||||
|
|
||||||
describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const subscriptionMocks = getSubscriptionMocks();
|
|
||||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription(subscriptionMocks);
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.getByTestId('All').click();
|
|
||||||
cy.wait('@Markets');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders orders', () => {
|
|
||||||
cy.getByTestId('tab-orders').should('be.visible');
|
|
||||||
cy.getByTestId(cancelAllOrdersBtn).should('be.visible');
|
|
||||||
cy.getByTestId(cancelOrderBtn).should('have.length.at.least', 1);
|
|
||||||
cy.getByTestId(editOrderBtn).should('have.length.at.least', 1);
|
|
||||||
cy.getByTestId('tab-orders').within(() => {
|
|
||||||
cy.get(`[role='rowgroup']`)
|
|
||||||
.first()
|
|
||||||
.within(() => {
|
|
||||||
cy.get(`[col-id='${orderSymbol}']`).each(($symbol) => {
|
|
||||||
cy.wrap($symbol).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderRemaining}']`).each(($remaining) => {
|
|
||||||
cy.wrap($remaining).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderSize}']`).each(($size) => {
|
|
||||||
cy.wrap($size).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderType}']`).each(($type) => {
|
|
||||||
cy.wrap($type).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderStatus}']`).each(($status) => {
|
|
||||||
cy.wrap($status).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderPrice}']`).each(($price) => {
|
|
||||||
cy.wrap($price).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderTimeInForce}']`).each(($timeInForce) => {
|
|
||||||
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(`[col-id='${orderUpdatedAt}']`).each(($dateTime) => {
|
|
||||||
cy.wrap($dateTime).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('partially filled orders should not show close/edit buttons', () => {
|
|
||||||
const partiallyFilledId =
|
|
||||||
'94aead3ca92dc932efcb503631b03a410e2a5d4606cae6083e2406dc38e52f78';
|
|
||||||
|
|
||||||
cy.getByTestId('tab-orders').should('be.visible');
|
|
||||||
cy.get('.ag-header-container').within(() => {
|
|
||||||
cy.get('[col-id="status"]').realHover();
|
|
||||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
|
||||||
});
|
|
||||||
cy.contains('Partially Filled').click();
|
|
||||||
cy.getByTestId('All').click();
|
|
||||||
|
|
||||||
cy.get(`[row-id="${partiallyFilledId}"]`)
|
|
||||||
.eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(`[col-id='${orderStatus}']`).should(
|
|
||||||
'have.text',
|
|
||||||
'Partially Filled'
|
|
||||||
);
|
|
||||||
cy.get(`[col-id='${orderRemaining}']`).should('have.text', '7');
|
|
||||||
cy.get(`[col-id='${orderSize}']`).should('have.text', '-10');
|
|
||||||
cy.getByTestId(cancelOrderBtn).should('not.exist');
|
|
||||||
cy.getByTestId(editOrderBtn).should('not.exist');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('orders are sorted by most recent order', () => {
|
|
||||||
// 7003-MORD-002
|
|
||||||
const expectedOrderList = [
|
|
||||||
'BTCUSD.MF21',
|
|
||||||
'SOLUSD',
|
|
||||||
'AAPL.MF21',
|
|
||||||
'BTCUSD.MF21',
|
|
||||||
'BTCUSD.MF21',
|
|
||||||
];
|
|
||||||
cy.get('.ag-header-container').within(() => {
|
|
||||||
cy.get('[col-id="status"]').realHover();
|
|
||||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
|
||||||
});
|
|
||||||
cy.contains('Reset').click();
|
|
||||||
cy.getByTestId('All').click();
|
|
||||||
|
|
||||||
cy.getByTestId('tab-orders')
|
|
||||||
.get(
|
|
||||||
`.ag-center-cols-container [col-id='${orderSymbol}'] [data-testid="market-code"]`
|
|
||||||
)
|
|
||||||
.should('have.length.at.least', expectedOrderList.length)
|
|
||||||
.then(($symbols) => {
|
|
||||||
const symbolNames: string[] = [];
|
|
||||||
cy.wrap($symbols)
|
|
||||||
.each(($symbol) => {
|
|
||||||
cy.wrap($symbol)
|
|
||||||
.invoke('text')
|
|
||||||
.then((text) => {
|
|
||||||
symbolNames.push(text);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
expect(symbolNames).to.include.ordered.members(expectedOrderList);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('subscribe orders', { tags: '@smoke' }, () => {
|
|
||||||
let orderId = '0';
|
|
||||||
beforeEach(() => {
|
|
||||||
const subscriptionMocks = getSubscriptionMocks();
|
|
||||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription(subscriptionMocks);
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.getByTestId('All').click();
|
|
||||||
cy.getByTestId('tab-orders').within(() => {
|
|
||||||
cy.get('[col-id="status"][role="columnheader"]')
|
|
||||||
.focus()
|
|
||||||
.find('.ag-header-cell-menu-button')
|
|
||||||
.click();
|
|
||||||
cy.get('.ag-filter-apply-panel-button').click();
|
|
||||||
});
|
|
||||||
orderId = (parseInt(orderId, 10) + 1).toString();
|
|
||||||
});
|
|
||||||
// 7002-SORD-053
|
|
||||||
// 7002-SORD-040
|
|
||||||
// 7003-MORD-001
|
|
||||||
|
|
||||||
it('must see an active order', () => {
|
|
||||||
// 7002-SORD-041
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Active');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see an expired order', () => {
|
|
||||||
// 7002-SORD-042
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_EXPIRED,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Expired');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a cancelled order', () => {
|
|
||||||
// 7002-SORD-043
|
|
||||||
// NOT COVERED: see the txn that cancelled it and a link to the block explorer, if cancelled by a user transaction.
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_CANCELLED,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Cancelled');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a stopped order', () => {
|
|
||||||
// 7002-SORD-044
|
|
||||||
// NOT COVERED: see an explanation of why stopped
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_STOPPED,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Stopped');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a partially filled order', () => {
|
|
||||||
// 7002-SORD-045
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_PARTIALLY_FILLED,
|
|
||||||
size: '5',
|
|
||||||
remaining: '1',
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should(
|
|
||||||
'have.text',
|
|
||||||
'Partially Filled'
|
|
||||||
);
|
|
||||||
cy.getByTestId(`order-status-${orderId}`)
|
|
||||||
.parentsUntil(`.ag-row`)
|
|
||||||
.siblings(`[col-id=${orderRemaining}]`)
|
|
||||||
.should('have.text', '4');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a filled order', () => {
|
|
||||||
// 7002-SORD-046
|
|
||||||
// 7003-MORD-020
|
|
||||||
// NOT COVERED: Must be able to see/link to all trades that were created from this order
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_FILLED,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
|
|
||||||
cy.get(`[col-id="${orderSymbol}"]`).contains('[title="Future"]', 'Futr');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a rejected order', () => {
|
|
||||||
// 7002-SORD-047
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_REJECTED,
|
|
||||||
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_INTERNAL_ERROR,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should(
|
|
||||||
'have.text',
|
|
||||||
'Rejected: Internal error'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a parked order', () => {
|
|
||||||
// 7002-SORD-048
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_PARKED,
|
|
||||||
});
|
|
||||||
cy.getByTestId(`order-status-${orderId}`).should(
|
|
||||||
'have.text',
|
|
||||||
'Parked: Internal error'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see the size of the order and direction/side -', () => {
|
|
||||||
// 7003-MORD-003
|
|
||||||
// 7003-MORD-004
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
size: '15',
|
|
||||||
side: Schema.Side.SIDE_SELL,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[col-id="${orderSize}"]`)
|
|
||||||
.should('have.text', '-15');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see the size of the order and direction/side +', () => {
|
|
||||||
// 7003-MORD-003
|
|
||||||
// 7003-MORD-004
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
size: '5',
|
|
||||||
side: Schema.Side.SIDE_BUY,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[col-id="${orderSize}"]`)
|
|
||||||
.should('have.text', '+5');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('for limit typy must see the Limit price that was set on the order', () => {
|
|
||||||
// 7003-MORD-005
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[col-id="price"]')
|
|
||||||
.should('have.text', '200.00');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a pegged order - ask', () => {
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
side: Schema.Side.SIDE_BUY,
|
|
||||||
peggedOrder: {
|
|
||||||
__typename: 'PeggedOrder',
|
|
||||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_ASK,
|
|
||||||
offset: '250000',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[col-id="type"]')
|
|
||||||
.should('have.text', 'Ask - 2.50 Peg limit');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a pegged order - bid', () => {
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
side: Schema.Side.SIDE_SELL,
|
|
||||||
peggedOrder: {
|
|
||||||
__typename: 'PeggedOrder',
|
|
||||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
|
||||||
offset: '100',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[col-id="type"]')
|
|
||||||
.should('have.text', 'Bid + 0.001 Peg limit');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see a pegged order - mid', () => {
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
side: Schema.Side.SIDE_SELL,
|
|
||||||
peggedOrder: {
|
|
||||||
__typename: 'PeggedOrder',
|
|
||||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_MID,
|
|
||||||
offset: '0.5',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[col-id="type"]')
|
|
||||||
.should('have.text', 'Mid + 0.00001 Peg limit');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('for market typy must not see a price for active or parked orders', () => {
|
|
||||||
// 7003-MORD-005
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
type: Schema.OrderType.TYPE_MARKET,
|
|
||||||
status: Schema.OrderStatus.STATUS_PARKED,
|
|
||||||
peggedOrder: null,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[col-id="price"]')
|
|
||||||
.should('have.text', '-');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must see the time in force applied to the order', () => {
|
|
||||||
// 7003-MORD-006
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
type: Schema.OrderType.TYPE_MARKET,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[col-id='${orderTimeInForce}']`)
|
|
||||||
.should('have.text', 'GTC');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('for Active order when is part of a liquidity or peg shape, must not see an option to amend the individual order ', () => {
|
|
||||||
// 7003-MORD-008
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: {},
|
|
||||||
liquidityProvisionId: '6536',
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="cancel"]`)
|
|
||||||
.should('not.exist');
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="edit"]`)
|
|
||||||
.should('not.exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('for Active order when is part of a liquidity, must not see an option to amend the individual order ', () => {
|
|
||||||
// 7003-MORD-008
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: {},
|
|
||||||
liquidityProvisionId: '6536',
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="cancel"]`)
|
|
||||||
.should('not.exist');
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="edit"]`)
|
|
||||||
.should('not.exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('for Active order when is part of a peg shape, must not see an option to amend the individual order ', () => {
|
|
||||||
// 7003-MORD-008
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: {},
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="cancel"]`)
|
|
||||||
.should('not.exist');
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="edit"]`)
|
|
||||||
.should('not.exist');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('amend and cancel order', { tags: '@smoke' }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const subscriptionMocks = getSubscriptionMocks();
|
|
||||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription(subscriptionMocks);
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.getByTestId('All').click();
|
|
||||||
cy.getByTestId('tab-orders').within(() => {
|
|
||||||
cy.get('[col-id="status"][role="columnheader"]')
|
|
||||||
.focus()
|
|
||||||
.find('.ag-header-cell-menu-button')
|
|
||||||
.click();
|
|
||||||
cy.get('.ag-filter-apply-panel-button').click();
|
|
||||||
});
|
|
||||||
cy.mockVegaWalletTransaction();
|
|
||||||
});
|
|
||||||
|
|
||||||
const orderId = '1234567890';
|
|
||||||
|
|
||||||
// this test is flakey
|
|
||||||
it('must be able to amend the price of an order', () => {
|
|
||||||
// 7003-MORD-007
|
|
||||||
// 7003-MORD-012
|
|
||||||
// 7003-MORD-014
|
|
||||||
// 7003-MORD-015
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: null,
|
|
||||||
liquidityProvisionId: null,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[data-testid="icon-edit"]')
|
|
||||||
.then(($btn) => {
|
|
||||||
cy.wrap($btn).click();
|
|
||||||
cy.getByTestId('dialog-title').should('have.text', 'Edit order');
|
|
||||||
cy.get('#limitPrice').focus().clear().type('100');
|
|
||||||
cy.getByTestId('edit-order').find('[type="submit"]').click();
|
|
||||||
const order: OrderAmendment = {
|
|
||||||
orderId: orderId,
|
|
||||||
marketId: 'market-0',
|
|
||||||
price: '10000000',
|
|
||||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
|
||||||
sizeDelta: 0,
|
|
||||||
};
|
|
||||||
testOrderAmendment(order);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('must be able to cancel an individual order', () => {
|
|
||||||
// 7003-MORD-007
|
|
||||||
// 7003-MORD-009
|
|
||||||
// 7003-MORD-010
|
|
||||||
// 7003-MORD-011
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: null,
|
|
||||||
liquidityProvisionId: null,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find(`[data-testid="icon-cross"]`)
|
|
||||||
.then(($btn) => {
|
|
||||||
cy.wrap($btn).click({ force: true });
|
|
||||||
const order: OrderCancellation = {
|
|
||||||
orderId: orderId,
|
|
||||||
marketId: 'market-0',
|
|
||||||
};
|
|
||||||
testOrderCancellation(order);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('must be able to cancel all orders on all markets', () => {
|
|
||||||
// 7003-MORD-009
|
|
||||||
// 7003-MORD-010
|
|
||||||
// 7003-MORD-011
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: null,
|
|
||||||
liquidityProvisionId: null,
|
|
||||||
});
|
|
||||||
cy.get(`[data-testid="cancelAll"]`)
|
|
||||||
.should('have.text', 'Cancel all')
|
|
||||||
.then(($btn) => {
|
|
||||||
cy.wrap($btn).click({ force: true });
|
|
||||||
const order: OrderCancellation = {};
|
|
||||||
testOrderCancellation(order);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('must be warned (pre-submit) if the input price has too many digits after the decimal place for the market', () => {
|
|
||||||
// 7003-MORD-013
|
|
||||||
updateOrder({
|
|
||||||
id: orderId,
|
|
||||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
|
||||||
peggedOrder: null,
|
|
||||||
liquidityProvisionId: null,
|
|
||||||
});
|
|
||||||
cy.get(`[row-id=${orderId}]`)
|
|
||||||
.find('[data-testid="icon-edit"]')
|
|
||||||
.then(($btn) => {
|
|
||||||
cy.wrap($btn).click({ force: true });
|
|
||||||
cy.getByTestId('dialog-title').should('have.text', 'Edit order');
|
|
||||||
cy.get('#limitPrice').focus().clear().type('0.111111');
|
|
||||||
cy.getByTestId('edit-order').find('[type="submit"]').click();
|
|
||||||
cy.getByTestId('input-error-text').should(
|
|
||||||
'have.text',
|
|
||||||
'Price accepts up to 5 decimal places'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { selectAsset } from '../support/helpers';
|
|
||||||
|
|
||||||
const amountField = 'input[name="amount"]';
|
|
||||||
const transferText = 'transfer-intro-text';
|
|
||||||
const errorText = 'input-error-text';
|
|
||||||
const formFieldError = 'input-error-text';
|
|
||||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
|
||||||
const manageVegaWallet = 'manage-vega-wallet';
|
|
||||||
const submitTransferBtn = '[type="submit"]';
|
|
||||||
const toAddressField = '[name="toAddress"]';
|
|
||||||
const transferForm = 'transfer-form';
|
|
||||||
const walletTransfer = 'wallet-transfer';
|
|
||||||
|
|
||||||
const ASSET_EURO = 1;
|
|
||||||
const ASSET_SEPOLIA_TBTC = 2;
|
|
||||||
|
|
||||||
describe(
|
|
||||||
'transfer form validation',
|
|
||||||
{ tags: '@regression', testIsolation: true },
|
|
||||||
() => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.getByTestId(manageVegaWallet).click();
|
|
||||||
cy.getByTestId(walletTransfer).click();
|
|
||||||
|
|
||||||
cy.wait('@Accounts');
|
|
||||||
cy.wait('@Assets');
|
|
||||||
|
|
||||||
cy.mockVegaWalletTransaction();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('transfer Text', () => {
|
|
||||||
// 1003-TRAN-003
|
|
||||||
cy.getByTestId(transferText)
|
|
||||||
.should('exist')
|
|
||||||
.get(keyID)
|
|
||||||
.invoke('text')
|
|
||||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid vega key validation', () => {
|
|
||||||
//1003-TRAN-013
|
|
||||||
//1003-TRAN-004
|
|
||||||
cy.getByTestId(transferForm).should('be.visible');
|
|
||||||
cy.contains('Enter manually').click();
|
|
||||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
|
||||||
cy.contains('label', 'Vega key').should('be.visible');
|
|
||||||
cy.contains('label', 'Asset').should('be.visible');
|
|
||||||
cy.contains('label', 'Amount').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('empty fields', () => {
|
|
||||||
// 1003-TRAN-012
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
|
||||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('min amount', () => {
|
|
||||||
// 1002-WITH-010
|
|
||||||
// 1003-TRAN-014
|
|
||||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
|
||||||
cy.get(amountField).clear().type('0');
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(errorText).should(
|
|
||||||
'contain.text',
|
|
||||||
'Value is below minimum'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('max amount', () => {
|
|
||||||
// 1003-TRAN-002
|
|
||||||
// 1003-TRAN-011
|
|
||||||
// 1003-TRAN-002
|
|
||||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
|
||||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(errorText).should(
|
|
||||||
'contain.text',
|
|
||||||
'You cannot transfer more than your available collateral'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import MarketPage from '../market';
|
|
||||||
|
|
||||||
export const ClosedMarketPage = () => {
|
|
||||||
return <MarketPage closed />;
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { ClosedMarketPage as default } from './closed-market';
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
SidebarButton,
|
||||||
|
SidebarDivider,
|
||||||
|
ViewType,
|
||||||
|
} from '../../components/sidebar';
|
||||||
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
|
|
||||||
|
export const LiquiditySidebar = () => {
|
||||||
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Deposit}
|
||||||
|
icon={VegaIconNames.DEPOSIT}
|
||||||
|
tooltip={t('Deposit')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Withdraw}
|
||||||
|
icon={VegaIconNames.WITHDRAW}
|
||||||
|
tooltip={t('Withdraw')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Transfer}
|
||||||
|
icon={VegaIconNames.TRANSFER}
|
||||||
|
tooltip={t('Transfer')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarDivider />
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Order}
|
||||||
|
icon={VegaIconNames.TICKET}
|
||||||
|
tooltip={t('Order')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Info}
|
||||||
|
icon={VegaIconNames.BREAKDOWN}
|
||||||
|
tooltip={t('Market specification')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -43,7 +43,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
|||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-8">
|
<>
|
||||||
<HeaderStat heading={t('Mark Price')} testId="market-price">
|
<HeaderStat heading={t('Mark Price')} testId="market-price">
|
||||||
<MarketMarkPrice
|
<MarketMarkPrice
|
||||||
marketId={market.id}
|
marketId={market.id}
|
||||||
@@ -86,6 +86,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
|||||||
<MarketLiquiditySupplied
|
<MarketLiquiditySupplied
|
||||||
marketId={market.id}
|
marketId={market.id}
|
||||||
assetDecimals={asset?.decimals || 0}
|
assetDecimals={asset?.decimals || 0}
|
||||||
|
quantum={asset.quantum}
|
||||||
/>
|
/>
|
||||||
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
|
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
|
||||||
<HeaderStat
|
<HeaderStat
|
||||||
@@ -144,7 +145,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
|||||||
</HeaderStat>
|
</HeaderStat>
|
||||||
)}
|
)}
|
||||||
<MarketProposalNotification marketId={market.id} />
|
<MarketProposalNotification marketId={market.id} />
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import { useGlobalStore, usePageTitleStore } from '../../stores';
|
|||||||
import { TradeGrid } from './trade-grid';
|
import { TradeGrid } from './trade-grid';
|
||||||
import { TradePanels } from './trade-panels';
|
import { TradePanels } from './trade-panels';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Links, Routes } from '../../lib/links';
|
import { Links } from '../../lib/links';
|
||||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
||||||
return markPrice && decimalPlaces
|
return markPrice && decimalPlaces
|
||||||
@@ -57,7 +56,7 @@ const TitleUpdater = ({
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
export const MarketPage = () => {
|
||||||
const { marketId } = useParams();
|
const { marketId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
@@ -70,25 +69,11 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
|||||||
|
|
||||||
const { data, loading } = useMarket(marketId);
|
const { data, loading } = useMarket(marketId);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
data?.state &&
|
|
||||||
[
|
|
||||||
MarketState.STATE_SETTLED,
|
|
||||||
MarketState.STATE_TRADING_TERMINATED,
|
|
||||||
].includes(data.state) &&
|
|
||||||
currentRouteId !== Routes.CLOSED_MARKETS &&
|
|
||||||
marketId
|
|
||||||
) {
|
|
||||||
navigate(Links.CLOSED_MARKETS(marketId));
|
|
||||||
}
|
|
||||||
}, [data?.state, currentRouteId, navigate, marketId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.id && data.id !== lastMarketId && !closed) {
|
if (data?.id && data.id !== lastMarketId && !closed) {
|
||||||
update({ marketId: data.id });
|
update({ marketId: data.id });
|
||||||
}
|
}
|
||||||
}, [update, lastMarketId, data?.id, closed]);
|
}, [update, lastMarketId, data?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (largeScreen && view === undefined) {
|
if (largeScreen && view === undefined) {
|
||||||
@@ -97,7 +82,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
|||||||
currentRouteId
|
currentRouteId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [setViews, view, currentRouteId, largeScreen, closed]);
|
}, [setViews, view, currentRouteId, largeScreen]);
|
||||||
|
|
||||||
const pinnedAsset = data && getAsset(data);
|
const pinnedAsset = data && getAsset(data);
|
||||||
|
|
||||||
|
|||||||
@@ -73,10 +73,19 @@ const MainGrid = memo(
|
|||||||
{market &&
|
{market &&
|
||||||
market.tradableInstrument.instrument.product.__typename ===
|
market.tradableInstrument.instrument.product.__typename ===
|
||||||
'Perpetual' ? (
|
'Perpetual' ? (
|
||||||
<Tab id="funding" name={t('Funding')}>
|
<Tab id="funding-history" name={t('Funding history')}>
|
||||||
<TradingViews.funding.component marketId={marketId} />
|
<TradingViews.funding.component marketId={marketId} />
|
||||||
</Tab>
|
</Tab>
|
||||||
) : null}
|
) : null}
|
||||||
|
{market &&
|
||||||
|
market.tradableInstrument.instrument.product.__typename ===
|
||||||
|
'Perpetual' ? (
|
||||||
|
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||||
|
<TradingViews.fundingPayments.component
|
||||||
|
marketId={marketId}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
) : null}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TradeGridChild>
|
</TradeGridChild>
|
||||||
</ResizableGridPanel>
|
</ResizableGridPanel>
|
||||||
@@ -114,7 +123,7 @@ const MainGrid = memo(
|
|||||||
<Tab
|
<Tab
|
||||||
id="open-orders"
|
id="open-orders"
|
||||||
name={t('Open')}
|
name={t('Open')}
|
||||||
menu={<TradingViews.activeOrders.menu marketId={marketId} />}
|
menu={<TradingViews.activeOrders.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.orders.component filter={Filter.Open} />
|
<TradingViews.orders.component filter={Filter.Open} />
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -127,7 +136,7 @@ const MainGrid = memo(
|
|||||||
<Tab
|
<Tab
|
||||||
id="orders"
|
id="orders"
|
||||||
name={t('All')}
|
name={t('All')}
|
||||||
menu={<TradingViews.orders.menu marketId={marketId} />}
|
menu={<TradingViews.orders.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.orders.component />
|
<TradingViews.orders.component />
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -139,9 +148,6 @@ const MainGrid = memo(
|
|||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<TradingViews.fills.component />
|
<TradingViews.fills.component />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
|
||||||
<TradingViews.fundingPayments.component />
|
|
||||||
</Tab>
|
|
||||||
<Tab
|
<Tab
|
||||||
id="accounts"
|
id="accounts"
|
||||||
name={t('Collateral')}
|
name={t('Collateral')}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||||
<Menu marketId={market?.id || ''} />
|
<Menu />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,12 +363,12 @@ describe('Closed', () => {
|
|||||||
const container = within(
|
const container = within(
|
||||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||||
);
|
);
|
||||||
const cell = container.getAllByRole('gridcell', {
|
|
||||||
name: (_name, element) => element.getAttribute('col-id') === 'code',
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
|
const cells = await container.findAllByRole('gridcell');
|
||||||
'PRNT'
|
const cell = cells.find((el) => el.getAttribute('col-id') === 'code');
|
||||||
);
|
|
||||||
|
expect(
|
||||||
|
within(cell as HTMLElement).getByTestId('stack-cell-secondary')
|
||||||
|
).toHaveTextContent('PRNT');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { CellClickedEvent } from 'ag-grid-community';
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import { isAfter } from 'date-fns';
|
import { isAfter } from 'date-fns';
|
||||||
import type {
|
import type {
|
||||||
@@ -5,6 +6,7 @@ import type {
|
|||||||
VegaValueFormatterParams,
|
VegaValueFormatterParams,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type { Asset } from '@vegaprotocol/types';
|
import type { Asset } from '@vegaprotocol/types';
|
||||||
@@ -17,13 +19,11 @@ import {
|
|||||||
import { closedMarketsWithDataProvider, getAsset } from '@vegaprotocol/markets';
|
import { closedMarketsWithDataProvider, getAsset } from '@vegaprotocol/markets';
|
||||||
import type { DataSourceFilterFragment } from '@vegaprotocol/markets';
|
import type { DataSourceFilterFragment } from '@vegaprotocol/markets';
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
import { SettlementDateCell } from './settlement-date-cell';
|
import { SettlementDateCell } from './settlement-date-cell';
|
||||||
import { SettlementPriceCell } from './settlement-price-cell';
|
import { SettlementPriceCell } from './settlement-price-cell';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { MarketCodeCell } from './market-code-cell';
|
import { MarketCodeCell } from './market-code-cell';
|
||||||
import { MarketActionsDropdown } from './market-table-actions';
|
import { MarketActionsDropdown } from './market-table-actions';
|
||||||
import type { CellClickedEvent } from 'ag-grid-community';
|
|
||||||
import { useClosedMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
|
||||||
|
|
||||||
type SettlementAsset = Pick<
|
type SettlementAsset = Pick<
|
||||||
Asset,
|
Asset,
|
||||||
@@ -127,7 +127,7 @@ const ClosedMarketsDataGrid = ({
|
|||||||
rowData: Row[];
|
rowData: Row[];
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const handleOnSelect = useClosedMarketClickHandler();
|
const handleOnSelect = useMarketClickHandler();
|
||||||
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||||
|
|
||||||
const colDefs = useMemo(() => {
|
const colDefs = useMemo(() => {
|
||||||
@@ -302,8 +302,11 @@ const ClosedMarketsDataGrid = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore metaKey exists
|
handleOnSelect(
|
||||||
handleOnSelect(data.id, event ? event.metaKey : false);
|
data.id,
|
||||||
|
// @ts-ignore metaKey exists
|
||||||
|
event ? event.metaKey : false
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Route, Routes, useParams } from 'react-router-dom';
|
||||||
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { useMarket } from '@vegaprotocol/markets';
|
||||||
|
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
SidebarButton,
|
||||||
|
SidebarDivider,
|
||||||
|
ViewType,
|
||||||
|
} from '../../components/sidebar';
|
||||||
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
|
|
||||||
|
export const MarketsSidebar = () => {
|
||||||
|
const { marketId } = useParams();
|
||||||
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
|
const { data } = useMarket(marketId);
|
||||||
|
const active =
|
||||||
|
data &&
|
||||||
|
[MarketState.STATE_ACTIVE, MarketState.STATE_PENDING].includes(data.state);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Deposit}
|
||||||
|
icon={VegaIconNames.DEPOSIT}
|
||||||
|
tooltip={t('Deposit')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Withdraw}
|
||||||
|
icon={VegaIconNames.WITHDRAW}
|
||||||
|
tooltip={t('Withdraw')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Transfer}
|
||||||
|
icon={VegaIconNames.TRANSFER}
|
||||||
|
tooltip={t('Transfer')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path=":marketId"
|
||||||
|
element={
|
||||||
|
<>
|
||||||
|
<SidebarDivider />
|
||||||
|
{active && (
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Order}
|
||||||
|
icon={VegaIconNames.TICKET}
|
||||||
|
tooltip={t('Order')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Info}
|
||||||
|
icon={VegaIconNames.BREAKDOWN}
|
||||||
|
tooltip={t('Market specification')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
query AccountHistory(
|
|
||||||
$partyId: ID!
|
|
||||||
$assetId: ID!
|
|
||||||
$accountTypes: [AccountType!]
|
|
||||||
$dateRange: DateRange
|
|
||||||
$marketIds: [ID!]
|
|
||||||
) {
|
|
||||||
balanceChanges(
|
|
||||||
filter: {
|
|
||||||
partyIds: [$partyId]
|
|
||||||
accountTypes: $accountTypes
|
|
||||||
assetId: $assetId
|
|
||||||
marketIds: $marketIds
|
|
||||||
}
|
|
||||||
dateRange: $dateRange
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
timestamp
|
|
||||||
partyId
|
|
||||||
balance
|
|
||||||
marketId
|
|
||||||
assetId
|
|
||||||
accountType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query AccountsWithBalance($partyId: ID!, $dateRange: DateRange) {
|
|
||||||
balanceChanges(filter: { partyIds: [$partyId] }, dateRange: $dateRange) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
assetId
|
|
||||||
accountType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import * as Types from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
import { gql } from '@apollo/client';
|
|
||||||
import * as Apollo from '@apollo/client';
|
|
||||||
const defaultOptions = {} as const;
|
|
||||||
export type AccountHistoryQueryVariables = Types.Exact<{
|
|
||||||
partyId: Types.Scalars['ID'];
|
|
||||||
assetId: Types.Scalars['ID'];
|
|
||||||
accountTypes?: Types.InputMaybe<Array<Types.AccountType> | Types.AccountType>;
|
|
||||||
dateRange?: Types.InputMaybe<Types.DateRange>;
|
|
||||||
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type AccountHistoryQuery = { __typename?: 'Query', balanceChanges: { __typename?: 'AggregatedBalanceConnection', edges: Array<{ __typename?: 'AggregatedBalanceEdge', node: { __typename?: 'AggregatedBalance', timestamp: any, partyId?: string | null, balance: string, marketId?: string | null, assetId?: string | null, accountType?: Types.AccountType | null } } | null> } };
|
|
||||||
|
|
||||||
export type AccountsWithBalanceQueryVariables = Types.Exact<{
|
|
||||||
partyId: Types.Scalars['ID'];
|
|
||||||
dateRange?: Types.InputMaybe<Types.DateRange>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type AccountsWithBalanceQuery = { __typename?: 'Query', balanceChanges: { __typename?: 'AggregatedBalanceConnection', edges: Array<{ __typename?: 'AggregatedBalanceEdge', node: { __typename?: 'AggregatedBalance', assetId?: string | null, accountType?: Types.AccountType | null } } | null> } };
|
|
||||||
|
|
||||||
|
|
||||||
export const AccountHistoryDocument = gql`
|
|
||||||
query AccountHistory($partyId: ID!, $assetId: ID!, $accountTypes: [AccountType!], $dateRange: DateRange, $marketIds: [ID!]) {
|
|
||||||
balanceChanges(
|
|
||||||
filter: {partyIds: [$partyId], accountTypes: $accountTypes, assetId: $assetId, marketIds: $marketIds}
|
|
||||||
dateRange: $dateRange
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
timestamp
|
|
||||||
partyId
|
|
||||||
balance
|
|
||||||
marketId
|
|
||||||
assetId
|
|
||||||
accountType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useAccountHistoryQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useAccountHistoryQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useAccountHistoryQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
|
||||||
* you can use to render your UI.
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { data, loading, error } = useAccountHistoryQuery({
|
|
||||||
* variables: {
|
|
||||||
* partyId: // value for 'partyId'
|
|
||||||
* assetId: // value for 'assetId'
|
|
||||||
* accountTypes: // value for 'accountTypes'
|
|
||||||
* dateRange: // value for 'dateRange'
|
|
||||||
* marketIds: // value for 'marketIds'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useAccountHistoryQuery(baseOptions: Apollo.QueryHookOptions<AccountHistoryQuery, AccountHistoryQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<AccountHistoryQuery, AccountHistoryQueryVariables>(AccountHistoryDocument, options);
|
|
||||||
}
|
|
||||||
export function useAccountHistoryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AccountHistoryQuery, AccountHistoryQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<AccountHistoryQuery, AccountHistoryQueryVariables>(AccountHistoryDocument, options);
|
|
||||||
}
|
|
||||||
export type AccountHistoryQueryHookResult = ReturnType<typeof useAccountHistoryQuery>;
|
|
||||||
export type AccountHistoryLazyQueryHookResult = ReturnType<typeof useAccountHistoryLazyQuery>;
|
|
||||||
export type AccountHistoryQueryResult = Apollo.QueryResult<AccountHistoryQuery, AccountHistoryQueryVariables>;
|
|
||||||
export const AccountsWithBalanceDocument = gql`
|
|
||||||
query AccountsWithBalance($partyId: ID!, $dateRange: DateRange) {
|
|
||||||
balanceChanges(filter: {partyIds: [$partyId]}, dateRange: $dateRange) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
assetId
|
|
||||||
accountType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useAccountsWithBalanceQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useAccountsWithBalanceQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useAccountsWithBalanceQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
|
||||||
* you can use to render your UI.
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { data, loading, error } = useAccountsWithBalanceQuery({
|
|
||||||
* variables: {
|
|
||||||
* partyId: // value for 'partyId'
|
|
||||||
* dateRange: // value for 'dateRange'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useAccountsWithBalanceQuery(baseOptions: Apollo.QueryHookOptions<AccountsWithBalanceQuery, AccountsWithBalanceQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<AccountsWithBalanceQuery, AccountsWithBalanceQueryVariables>(AccountsWithBalanceDocument, options);
|
|
||||||
}
|
|
||||||
export function useAccountsWithBalanceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AccountsWithBalanceQuery, AccountsWithBalanceQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<AccountsWithBalanceQuery, AccountsWithBalanceQueryVariables>(AccountsWithBalanceDocument, options);
|
|
||||||
}
|
|
||||||
export type AccountsWithBalanceQueryHookResult = ReturnType<typeof useAccountsWithBalanceQuery>;
|
|
||||||
export type AccountsWithBalanceLazyQueryHookResult = ReturnType<typeof useAccountsWithBalanceLazyQuery>;
|
|
||||||
export type AccountsWithBalanceQueryResult = Apollo.QueryResult<AccountsWithBalanceQuery, AccountsWithBalanceQueryVariables>;
|
|
||||||
@@ -1,349 +0,0 @@
|
|||||||
import { addDecimal, fromNanoSeconds } from '@vegaprotocol/utils';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
import compact from 'lodash/compact';
|
|
||||||
import uniqBy from 'lodash/uniqBy';
|
|
||||||
import type { ChangeEvent } from 'react';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
|
||||||
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
|
||||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
|
||||||
import {
|
|
||||||
AsyncRenderer,
|
|
||||||
Splash,
|
|
||||||
Toggle,
|
|
||||||
TradingButton,
|
|
||||||
TradingDropdown,
|
|
||||||
TradingDropdownContent,
|
|
||||||
TradingDropdownItem,
|
|
||||||
TradingDropdownTrigger,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
|
||||||
import { PriceChart } from 'pennant';
|
|
||||||
import 'pennant/dist/style.css';
|
|
||||||
import type { Account } from '@vegaprotocol/accounts';
|
|
||||||
import { accountsDataProvider } from '@vegaprotocol/accounts';
|
|
||||||
import {
|
|
||||||
useLocalStorageSnapshot,
|
|
||||||
useThemeSwitcher,
|
|
||||||
} from '@vegaprotocol/react-helpers';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { getAsset, type Market } from '@vegaprotocol/markets';
|
|
||||||
|
|
||||||
export const DateRange = {
|
|
||||||
RANGE_1D: '1D',
|
|
||||||
RANGE_7D: '7D',
|
|
||||||
RANGE_1M: '1M',
|
|
||||||
RANGE_3M: '3M',
|
|
||||||
RANGE_1Y: '1Y',
|
|
||||||
RANGE_YTD: 'YTD',
|
|
||||||
RANGE_ALL: 'All',
|
|
||||||
};
|
|
||||||
|
|
||||||
const dateRangeToggleItems = Object.entries(DateRange).map(([_, value]) => ({
|
|
||||||
label: t(value),
|
|
||||||
value: value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const calculateStartDate = (range: string): string | undefined => {
|
|
||||||
const now = new Date();
|
|
||||||
switch (range) {
|
|
||||||
case DateRange.RANGE_1D:
|
|
||||||
return new Date(now.setDate(now.getDate() - 1)).toISOString();
|
|
||||||
case DateRange.RANGE_7D:
|
|
||||||
return new Date(now.setDate(now.getDate() - 7)).toISOString();
|
|
||||||
case DateRange.RANGE_1M:
|
|
||||||
return new Date(now.setMonth(now.getMonth() - 1)).toISOString();
|
|
||||||
case DateRange.RANGE_3M:
|
|
||||||
return new Date(now.setMonth(now.getMonth() - 3)).toISOString();
|
|
||||||
case DateRange.RANGE_1Y:
|
|
||||||
return new Date(now.setFullYear(now.getFullYear() - 1)).toISOString();
|
|
||||||
case DateRange.RANGE_YTD:
|
|
||||||
return new Date(now.setMonth(0)).toISOString();
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AccountHistoryContainer = () => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
|
||||||
const { data: assets } = useAssetsDataProvider();
|
|
||||||
|
|
||||||
if (!pubKey) {
|
|
||||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AsyncRenderer loading={!assets} error={undefined} data={assets}>
|
|
||||||
{assets && <AccountHistoryManager pubKey={pubKey} assetData={assets} />}
|
|
||||||
</AsyncRenderer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountHistoryManager = ({
|
|
||||||
pubKey,
|
|
||||||
assetData,
|
|
||||||
}: {
|
|
||||||
pubKey: string;
|
|
||||||
assetData: AssetFieldsFragment[];
|
|
||||||
}) => {
|
|
||||||
const [accountType, setAccountType] = useState<Schema.AccountType>(
|
|
||||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL
|
|
||||||
);
|
|
||||||
|
|
||||||
const variablesForOneTimeQuery = useMemo(
|
|
||||||
() => ({
|
|
||||||
partyId: pubKey,
|
|
||||||
}),
|
|
||||||
[pubKey]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: accounts } = useDataProvider({
|
|
||||||
dataProvider: accountsDataProvider,
|
|
||||||
variables: variablesForOneTimeQuery,
|
|
||||||
skip: !pubKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
const assetIds = useMemo(
|
|
||||||
() => accounts?.map((e) => e?.asset?.id) || [],
|
|
||||||
[accounts]
|
|
||||||
);
|
|
||||||
|
|
||||||
const assets = useMemo(
|
|
||||||
() =>
|
|
||||||
assetData
|
|
||||||
.filter((a) => assetIds.includes(a.id))
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
||||||
[assetData, assetIds]
|
|
||||||
);
|
|
||||||
const [assetId, setAssetId] = useLocalStorageSnapshot(
|
|
||||||
'account-history-active-asset-id'
|
|
||||||
);
|
|
||||||
|
|
||||||
const asset = useMemo(
|
|
||||||
() => assets.find((a) => a.id === assetId) || assets[0],
|
|
||||||
[assetId, assets]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [range, setRange] = useState<typeof DateRange[keyof typeof DateRange]>(
|
|
||||||
DateRange.RANGE_1M
|
|
||||||
);
|
|
||||||
const [market, setMarket] = useState<Market | null>(null);
|
|
||||||
|
|
||||||
const marketFilterCb = useCallback(
|
|
||||||
(item: Market) => {
|
|
||||||
const itemAsset = getAsset(item);
|
|
||||||
return !asset?.id || itemAsset?.id === asset?.id;
|
|
||||||
},
|
|
||||||
[asset?.id]
|
|
||||||
);
|
|
||||||
const markets = useMemo<Market[] | null>(() => {
|
|
||||||
const arr =
|
|
||||||
accounts
|
|
||||||
?.filter((item: Account) => Boolean(item && item.market))
|
|
||||||
.map<Market>((item) => item.market as Market) ?? null;
|
|
||||||
return arr
|
|
||||||
? uniqBy(arr.filter(marketFilterCb), 'id').sort((a, b) =>
|
|
||||||
a.tradableInstrument.instrument.code.localeCompare(
|
|
||||||
b.tradableInstrument.instrument.code
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
}, [accounts, marketFilterCb]);
|
|
||||||
|
|
||||||
const resolveMarket = useCallback(
|
|
||||||
(m: Market) => {
|
|
||||||
setMarket(m);
|
|
||||||
const itemAsset = getAsset(m);
|
|
||||||
const newAssetId = itemAsset?.id;
|
|
||||||
const newAsset = assets.find((item) => item.id === newAssetId);
|
|
||||||
if ((!asset || (assets && newAssetId !== asset.id)) && newAsset) {
|
|
||||||
setAssetId(newAsset.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[asset, assets, setAssetId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const variables = useMemo(
|
|
||||||
() => ({
|
|
||||||
partyId: pubKey,
|
|
||||||
assetId: asset?.id || '',
|
|
||||||
accountTypes: accountType ? [accountType] : undefined,
|
|
||||||
dateRange:
|
|
||||||
range === 'All' ? undefined : { start: calculateStartDate(range) },
|
|
||||||
marketIds: market?.id ? [market.id] : undefined,
|
|
||||||
}),
|
|
||||||
[pubKey, asset, accountType, range, market?.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data } = useAccountHistoryQuery({
|
|
||||||
variables,
|
|
||||||
skip: !asset || !pubKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col h-full gap-2">
|
|
||||||
<div className="flex flex-wrap justify-between px-1 pt-2 gap-2">
|
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
|
||||||
<TradingDropdown
|
|
||||||
trigger={
|
|
||||||
<TradingDropdownTrigger>
|
|
||||||
<TradingButton size="small">
|
|
||||||
{accountType
|
|
||||||
? `${
|
|
||||||
AccountTypeMapping[
|
|
||||||
accountType as keyof typeof Schema.AccountType
|
|
||||||
]
|
|
||||||
} Account`
|
|
||||||
: t('Select account type')}
|
|
||||||
</TradingButton>
|
|
||||||
</TradingDropdownTrigger>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TradingDropdownContent>
|
|
||||||
{[
|
|
||||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
Schema.AccountType.ACCOUNT_TYPE_BOND,
|
|
||||||
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
|
||||||
].map((type) => (
|
|
||||||
<TradingDropdownItem
|
|
||||||
key={type}
|
|
||||||
onClick={() => {
|
|
||||||
setAccountType(type as Schema.AccountType);
|
|
||||||
|
|
||||||
// if not a margin account clear any market selection
|
|
||||||
if (type !== Schema.AccountType.ACCOUNT_TYPE_MARGIN) {
|
|
||||||
setMarket(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
|
|
||||||
</TradingDropdownItem>
|
|
||||||
))}
|
|
||||||
</TradingDropdownContent>
|
|
||||||
</TradingDropdown>
|
|
||||||
|
|
||||||
<TradingDropdown
|
|
||||||
trigger={
|
|
||||||
<TradingDropdownTrigger>
|
|
||||||
<TradingButton size="small">
|
|
||||||
{asset ? asset.symbol : t('Select asset')}
|
|
||||||
</TradingButton>
|
|
||||||
</TradingDropdownTrigger>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TradingDropdownContent>
|
|
||||||
{assets.map((a) => (
|
|
||||||
<TradingDropdownItem
|
|
||||||
key={a.id}
|
|
||||||
onClick={() => {
|
|
||||||
setAssetId(a.id);
|
|
||||||
|
|
||||||
// if the selected asset is different to the selected market clear the market
|
|
||||||
if (market && a.id !== getAsset(market).id) {
|
|
||||||
setMarket(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{a.symbol}
|
|
||||||
</TradingDropdownItem>
|
|
||||||
))}
|
|
||||||
</TradingDropdownContent>
|
|
||||||
</TradingDropdown>
|
|
||||||
<TradingDropdown
|
|
||||||
trigger={
|
|
||||||
<TradingDropdownTrigger>
|
|
||||||
<TradingButton size="small">
|
|
||||||
{market
|
|
||||||
? market.tradableInstrument.instrument.code
|
|
||||||
: t('Select market')}
|
|
||||||
</TradingButton>
|
|
||||||
</TradingDropdownTrigger>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TradingDropdownContent>
|
|
||||||
{market && (
|
|
||||||
<TradingDropdownItem key="0" onClick={() => setMarket(null)}>
|
|
||||||
{t('All markets')}
|
|
||||||
</TradingDropdownItem>
|
|
||||||
)}
|
|
||||||
{markets?.map((m) => (
|
|
||||||
<TradingDropdownItem
|
|
||||||
key={m.id}
|
|
||||||
onClick={() => resolveMarket(m)}
|
|
||||||
>
|
|
||||||
{m.tradableInstrument.instrument.code}
|
|
||||||
</TradingDropdownItem>
|
|
||||||
))}
|
|
||||||
</TradingDropdownContent>
|
|
||||||
</TradingDropdown>
|
|
||||||
</div>
|
|
||||||
<div className="justify-items-end">
|
|
||||||
<Toggle
|
|
||||||
id="account-history-date-range"
|
|
||||||
name="account-history-date-range"
|
|
||||||
toggles={dateRangeToggleItems}
|
|
||||||
checkedValue={range}
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setRange(e.target.value as keyof typeof DateRange)
|
|
||||||
}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
{asset && (
|
|
||||||
<div className="h-full">
|
|
||||||
<AccountHistoryChart
|
|
||||||
data={data}
|
|
||||||
accountType={accountType}
|
|
||||||
asset={asset}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AccountHistoryChart = ({
|
|
||||||
data,
|
|
||||||
accountType,
|
|
||||||
asset,
|
|
||||||
}: {
|
|
||||||
data: AccountHistoryQuery | undefined;
|
|
||||||
accountType: Schema.AccountType;
|
|
||||||
asset: AssetFieldsFragment;
|
|
||||||
}) => {
|
|
||||||
const { theme } = useThemeSwitcher();
|
|
||||||
const values: { cols: [string, string]; rows: [Date, number][] } | null =
|
|
||||||
useMemo(() => {
|
|
||||||
if (!data?.balanceChanges.edges.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const valuesData = compact(data.balanceChanges.edges)
|
|
||||||
.reduce((acc, edge) => {
|
|
||||||
if (edge.node.accountType === accountType) {
|
|
||||||
acc?.push({
|
|
||||||
datetime: fromNanoSeconds(edge.node.timestamp),
|
|
||||||
balance: Number(addDecimal(edge.node.balance, asset.decimals)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, [] as { datetime: Date; balance: number }[])
|
|
||||||
.reverse();
|
|
||||||
return {
|
|
||||||
cols: ['Date', `${asset.symbol} account balance`],
|
|
||||||
rows: compact(valuesData).map((d) => [d.datetime, d.balance]),
|
|
||||||
};
|
|
||||||
}, [accountType, asset.decimals, asset.symbol, data?.balanceChanges.edges]);
|
|
||||||
|
|
||||||
if (!data || !values?.rows.length) {
|
|
||||||
return <Splash> {t('No account history data')}</Splash>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <PriceChart data={values} theme={theme} />;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { SidebarButton, ViewType } from '../../components/sidebar';
|
||||||
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
|
|
||||||
|
export const PortfolioSidebar = () => {
|
||||||
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Deposit}
|
||||||
|
icon={VegaIconNames.DEPOSIT}
|
||||||
|
tooltip={t('Deposit')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Withdraw}
|
||||||
|
icon={VegaIconNames.WITHDRAW}
|
||||||
|
tooltip={t('Withdraw')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Transfer}
|
||||||
|
icon={VegaIconNames.TRANSFER}
|
||||||
|
tooltip={t('Transfer')}
|
||||||
|
routeId={currentRouteId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -15,7 +15,6 @@ import { PositionsMenu } from '../../components/positions-menu';
|
|||||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||||
import { OrdersContainer } from '../../components/orders-container';
|
import { OrdersContainer } from '../../components/orders-container';
|
||||||
import { LedgerContainer } from '../../components/ledger-container';
|
import { LedgerContainer } from '../../components/ledger-container';
|
||||||
import { AccountHistoryContainer } from './account-history-container';
|
|
||||||
import {
|
import {
|
||||||
ResizableGrid,
|
ResizableGrid,
|
||||||
ResizableGridPanel,
|
ResizableGridPanel,
|
||||||
@@ -66,10 +65,7 @@ export const Portfolio = () => {
|
|||||||
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||||
<ResizableGridPanel minSize={75}>
|
<ResizableGridPanel minSize={75}>
|
||||||
<PortfolioGridChild>
|
<PortfolioGridChild>
|
||||||
<Tabs storageKey="console-portfolio-top">
|
<Tabs storageKey="console-portfolio-top-1">
|
||||||
<Tab id="account-history" name={t('Account history')}>
|
|
||||||
<AccountHistoryContainer />
|
|
||||||
</Tab>
|
|
||||||
<Tab
|
<Tab
|
||||||
id="positions"
|
id="positions"
|
||||||
name={t('Positions')}
|
name={t('Positions')}
|
||||||
|
|||||||
+13
-17
@@ -1,38 +1,29 @@
|
|||||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||||
import {
|
import {
|
||||||
AppFailure,
|
AppFailure,
|
||||||
|
AppLoader,
|
||||||
DocsLinks,
|
DocsLinks,
|
||||||
NetworkLoader,
|
NetworkLoader,
|
||||||
|
NodeFailure,
|
||||||
NodeGuard,
|
NodeGuard,
|
||||||
useEnvironment,
|
useEnvironment,
|
||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { MaintenancePage } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { Web3Provider } from './web3-provider';
|
import { Web3Provider } from './web3-provider';
|
||||||
|
|
||||||
export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||||
loading: () => <>Loading...</>,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AppLoader = ({ children }: { children: ReactNode }) => {
|
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
VEGA_URL,
|
VEGA_URL,
|
||||||
VEGA_ENV,
|
VEGA_ENV,
|
||||||
VEGA_WALLET_URL,
|
VEGA_WALLET_URL,
|
||||||
VEGA_EXPLORER_URL,
|
VEGA_EXPLORER_URL,
|
||||||
MAINTENANCE_PAGE,
|
|
||||||
MOZILLA_EXTENSION_URL,
|
MOZILLA_EXTENSION_URL,
|
||||||
CHROME_EXTENSION_URL,
|
CHROME_EXTENSION_URL,
|
||||||
} = useEnvironment();
|
} = useEnvironment();
|
||||||
|
|
||||||
if (MAINTENANCE_PAGE) {
|
|
||||||
return <MaintenancePage />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!VEGA_URL ||
|
!VEGA_URL ||
|
||||||
!VEGA_WALLET_URL ||
|
!VEGA_WALLET_URL ||
|
||||||
@@ -41,22 +32,27 @@ export const AppLoader = ({ children }: { children: ReactNode }) => {
|
|||||||
!MOZILLA_EXTENSION_URL ||
|
!MOZILLA_EXTENSION_URL ||
|
||||||
!DocsLinks
|
!DocsLinks
|
||||||
) {
|
) {
|
||||||
return null;
|
return <AppLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NetworkLoader
|
<NetworkLoader
|
||||||
cache={cacheConfig}
|
cache={cacheConfig}
|
||||||
skeleton={<DynamicLoader />}
|
skeleton={<AppLoader />}
|
||||||
failure={
|
failure={
|
||||||
<AppFailure title={t('Could not initialize app')} error={error} />
|
<AppFailure title={t('Could not initialize app')} error={error} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<NodeGuard
|
<NodeGuard
|
||||||
skeleton={<DynamicLoader />}
|
skeleton={<AppLoader />}
|
||||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||||
>
|
>
|
||||||
<Web3Provider>
|
<Web3Provider
|
||||||
|
skeleton={<AppLoader />}
|
||||||
|
failure={
|
||||||
|
<AppFailure title={t(`Could not configure web3 provider`)} />
|
||||||
|
}
|
||||||
|
>
|
||||||
<VegaWalletProvider
|
<VegaWalletProvider
|
||||||
config={{
|
config={{
|
||||||
network: VEGA_ENV,
|
network: VEGA_ENV,
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
export * from './app-loader';
|
export * from './bootstrapper';
|
||||||
export * from './web3-provider';
|
export * from './web3-provider';
|
||||||
+22
-19
@@ -6,14 +6,20 @@ import {
|
|||||||
useWeb3ConnectStore,
|
useWeb3ConnectStore,
|
||||||
createDefaultProvider,
|
createDefaultProvider,
|
||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
export const Web3Provider = ({
|
||||||
|
children,
|
||||||
|
skeleton,
|
||||||
|
failure,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
skeleton: ReactNode;
|
||||||
|
failure: ReactNode;
|
||||||
|
}) => {
|
||||||
const { config, loading, error } = useEthereumConfig();
|
const { config, loading, error } = useEthereumConfig();
|
||||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||||
useEnvironment();
|
useEnvironment();
|
||||||
@@ -49,23 +55,20 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
|||||||
ETH_WALLET_MNEMONIC,
|
ETH_WALLET_MNEMONIC,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <>{failure}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading || !connectors.length) {
|
||||||
|
return <>{skeleton}</>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<Web3ProviderInternal
|
||||||
loading={loading}
|
connectors={connectors}
|
||||||
error={error}
|
defaultProvider={defaultProvider}
|
||||||
data={connectors}
|
|
||||||
noDataCondition={(d) => {
|
|
||||||
if (!d) return true;
|
|
||||||
return d.length < 1;
|
|
||||||
}}
|
|
||||||
noDataMessage={t('Could not fetch Ethereum configuration')}
|
|
||||||
>
|
>
|
||||||
<Web3ProviderInternal
|
<>{children}</>
|
||||||
connectors={connectors}
|
</Web3ProviderInternal>
|
||||||
defaultProvider={defaultProvider}
|
|
||||||
>
|
|
||||||
<>{children}</>
|
|
||||||
</Web3ProviderInternal>
|
|
||||||
</AsyncRenderer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -9,10 +9,36 @@ import { useMemo } from 'react';
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
|
||||||
DateRange,
|
const calculateStartDate = (range: string): string | undefined => {
|
||||||
calculateStartDate,
|
const now = new Date();
|
||||||
} from '../../client-pages/portfolio/account-history-container';
|
switch (range) {
|
||||||
|
case DateRange.RANGE_1D:
|
||||||
|
return new Date(now.setDate(now.getDate() - 1)).toISOString();
|
||||||
|
case DateRange.RANGE_7D:
|
||||||
|
return new Date(now.setDate(now.getDate() - 7)).toISOString();
|
||||||
|
case DateRange.RANGE_1M:
|
||||||
|
return new Date(now.setMonth(now.getMonth() - 1)).toISOString();
|
||||||
|
case DateRange.RANGE_3M:
|
||||||
|
return new Date(now.setMonth(now.getMonth() - 3)).toISOString();
|
||||||
|
case DateRange.RANGE_1Y:
|
||||||
|
return new Date(now.setFullYear(now.getFullYear() - 1)).toISOString();
|
||||||
|
case DateRange.RANGE_YTD:
|
||||||
|
return new Date(now.setMonth(0)).toISOString();
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DateRange = {
|
||||||
|
RANGE_1D: '1D',
|
||||||
|
RANGE_7D: '7D',
|
||||||
|
RANGE_1M: '1M',
|
||||||
|
RANGE_3M: '3M',
|
||||||
|
RANGE_1Y: '1Y',
|
||||||
|
RANGE_YTD: 'YTD',
|
||||||
|
RANGE_ALL: 'All',
|
||||||
|
};
|
||||||
|
|
||||||
export const FundingContainer = ({ marketId }: { marketId: string }) => {
|
export const FundingContainer = ({ marketId }: { marketId: string }) => {
|
||||||
const { theme } = useThemeSwitcher();
|
const { theme } = useThemeSwitcher();
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
|||||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
|
|
||||||
export const FundingPaymentsContainer = () => {
|
export const FundingPaymentsContainer = ({
|
||||||
|
marketId,
|
||||||
|
}: {
|
||||||
|
marketId?: string;
|
||||||
|
}) => {
|
||||||
const onMarketClick = useMarketClickHandler(true);
|
const onMarketClick = useMarketClickHandler(true);
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
@@ -33,6 +37,7 @@ export const FundingPaymentsContainer = () => {
|
|||||||
return (
|
return (
|
||||||
<FundingPaymentsManager
|
<FundingPaymentsManager
|
||||||
partyId={pubKey}
|
partyId={pubKey}
|
||||||
|
marketId={marketId}
|
||||||
onMarketClick={onMarketClick}
|
onMarketClick={onMarketClick}
|
||||||
gridProps={gridStoreCallbacks}
|
gridProps={gridStoreCallbacks}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<header className={headerClasses}>
|
<header className={headerClasses}>
|
||||||
<div className="hidden lg:flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
<div className="flex-col items-start justify-center hidden pt-2 pb-0 pl-3 lg:flex lg:pl-4 xl:pb-2">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div data-testid="header-summary" className="min-w-0">
|
<div data-testid="header-summary" className="min-w-0">
|
||||||
<div className="px-3 lg:px-4 py-2 flex flex-nowrap gap-4 items-center text-xs overflow-x-auto">
|
<div className="flex items-center px-3 py-2 overflow-x-auto text-xs lg:px-4 flex-nowrap gap-6">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +70,7 @@ export const HeaderTitle = ({ children }: { children: ReactNode }) => {
|
|||||||
return (
|
return (
|
||||||
<h1
|
<h1
|
||||||
data-testid="header-title"
|
data-testid="header-title"
|
||||||
className="flex gap-4 items-center text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default"
|
className="flex items-center text-lg gap-4 whitespace-nowrap xl:pr-4 xl:border-r border-default"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { Outlet, Routes, Route } from 'react-router-dom';
|
import type { ReactNode } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
import { Sidebar, SidebarContent, useSidebar } from '../sidebar';
|
import { Sidebar, SidebarContent, useSidebar } from '../sidebar';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MarketHeader } from '../market-header';
|
|
||||||
import { LiquidityHeader } from '../liquidity-header';
|
|
||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
|
|
||||||
export const LayoutWithSidebar = () => {
|
export const LayoutWithSidebar = ({
|
||||||
|
header,
|
||||||
|
sidebar,
|
||||||
|
}: {
|
||||||
|
header?: ReactNode;
|
||||||
|
sidebar?: ReactNode;
|
||||||
|
}) => {
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
const views = useSidebar((store) => store.views);
|
const views = useSidebar((store) => store.views);
|
||||||
const sidebarView = views[currentRouteId] || null;
|
const sidebarView = views[currentRouteId] || null;
|
||||||
@@ -20,16 +25,7 @@ export const LayoutWithSidebar = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={gridClasses}>
|
<div className={gridClasses}>
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">{header}</div>
|
||||||
<Routes>
|
|
||||||
<Route path="markets/:marketId" element={<MarketHeader />} />
|
|
||||||
<Route
|
|
||||||
path="markets/all/closed/:marketId"
|
|
||||||
element={<MarketHeader />}
|
|
||||||
/>
|
|
||||||
<Route path="liquidity/:marketId" element={<LiquidityHeader />} />
|
|
||||||
</Routes>
|
|
||||||
</div>
|
|
||||||
<main
|
<main
|
||||||
className={classNames('col-start-1 col-end-1', {
|
className={classNames('col-start-1 col-end-1', {
|
||||||
'lg:col-end-3': !sidebarOpen,
|
'lg:col-end-3': !sidebarOpen,
|
||||||
@@ -40,9 +36,12 @@ export const LayoutWithSidebar = () => {
|
|||||||
</main>
|
</main>
|
||||||
<aside
|
<aside
|
||||||
// min-h-0 is needed as this element is part of a grid, we want the content to be scrollable, without it it will push the grid element taller
|
// min-h-0 is needed as this element is part of a grid, we want the content to be scrollable, without it it will push the grid element taller
|
||||||
className={classNames('col-start-1 lg:col-start-2 min-h-0', {
|
className={classNames(
|
||||||
hidden: !sidebarOpen,
|
'col-start-1 lg:col-start-2 min-h-0 lg:border-l border-default lg:bg-vega-clight-800 dark:lg:bg-vega-cdark-800',
|
||||||
})}
|
{
|
||||||
|
hidden: !sidebarOpen,
|
||||||
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<SidebarContent />
|
<SidebarContent />
|
||||||
</aside>
|
</aside>
|
||||||
@@ -54,7 +53,7 @@ export const LayoutWithSidebar = () => {
|
|||||||
'lg:row-start-2 lg:row-span-full lg:col-start-3'
|
'lg:row-start-2 lg:row-span-full lg:col-start-3'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar options={sidebar} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import BigNumber from 'bignumber.js';
|
|||||||
import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity';
|
import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity';
|
||||||
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
|
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumberQuantum,
|
||||||
formatNumberPercentage,
|
formatNumberPercentage,
|
||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
@@ -30,12 +30,14 @@ interface Props {
|
|||||||
marketId?: string;
|
marketId?: string;
|
||||||
noUpdate?: boolean;
|
noUpdate?: boolean;
|
||||||
assetDecimals: number;
|
assetDecimals: number;
|
||||||
|
quantum: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MarketLiquiditySupplied = ({
|
export const MarketLiquiditySupplied = ({
|
||||||
marketId,
|
marketId,
|
||||||
assetDecimals,
|
assetDecimals,
|
||||||
noUpdate = false,
|
noUpdate = false,
|
||||||
|
quantum,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [market, setMarket] = useState<MarketData>();
|
const [market, setMarket] = useState<MarketData>();
|
||||||
const { params } = useNetworkParams([
|
const { params } = useNetworkParams([
|
||||||
@@ -79,11 +81,12 @@ export const MarketLiquiditySupplied = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const supplied = market?.suppliedStake
|
const supplied = market?.suppliedStake
|
||||||
? addDecimalsFormatNumber(
|
? addDecimalsFormatNumberQuantum(
|
||||||
new BigNumber(market?.suppliedStake)
|
new BigNumber(market?.suppliedStake)
|
||||||
.multipliedBy(stakeToCcyVolume || 1)
|
.multipliedBy(stakeToCcyVolume || 1)
|
||||||
.toString(),
|
.toString(),
|
||||||
assetDecimals
|
assetDecimals,
|
||||||
|
quantum
|
||||||
)
|
)
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
@@ -108,9 +111,10 @@ export const MarketLiquiditySupplied = ({
|
|||||||
<span>{t('Supplied stake')}</span>
|
<span>{t('Supplied stake')}</span>
|
||||||
<span>
|
<span>
|
||||||
{market?.suppliedStake
|
{market?.suppliedStake
|
||||||
? addDecimalsFormatNumber(
|
? addDecimalsFormatNumberQuantum(
|
||||||
new BigNumber(market?.suppliedStake).toString(),
|
market?.suppliedStake,
|
||||||
assetDecimals
|
assetDecimals,
|
||||||
|
quantum
|
||||||
)
|
)
|
||||||
: '-'}
|
: '-'}
|
||||||
</span>
|
</span>
|
||||||
@@ -119,9 +123,10 @@ export const MarketLiquiditySupplied = ({
|
|||||||
<span>{t('Target stake')}</span>
|
<span>{t('Target stake')}</span>
|
||||||
<span>
|
<span>
|
||||||
{market?.targetStake
|
{market?.targetStake
|
||||||
? addDecimalsFormatNumber(
|
? addDecimalsFormatNumberQuantum(
|
||||||
new BigNumber(market?.targetStake).toString(),
|
market?.targetStake,
|
||||||
assetDecimals
|
assetDecimals,
|
||||||
|
quantum
|
||||||
)
|
)
|
||||||
: '-'}
|
: '-'}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const market = {
|
|||||||
} as unknown as Market;
|
} as unknown as Market;
|
||||||
|
|
||||||
let mockDataSuccessorMarket: PartialDeep<Market> | null = null;
|
let mockDataSuccessorMarket: PartialDeep<Market> | null = null;
|
||||||
|
let mockDataMarketState: Market['state'] | null = null;
|
||||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||||
...jest.requireActual('@vegaprotocol/data-provider'),
|
...jest.requireActual('@vegaprotocol/data-provider'),
|
||||||
useDataProvider: jest.fn().mockImplementation((args) => {
|
useDataProvider: jest.fn().mockImplementation((args) => {
|
||||||
@@ -43,6 +44,12 @@ jest.mock('@vegaprotocol/utils', () => ({
|
|||||||
let mockCandles = {};
|
let mockCandles = {};
|
||||||
jest.mock('@vegaprotocol/markets', () => ({
|
jest.mock('@vegaprotocol/markets', () => ({
|
||||||
...jest.requireActual('@vegaprotocol/markets'),
|
...jest.requireActual('@vegaprotocol/markets'),
|
||||||
|
useMarketState: (marketId: string) =>
|
||||||
|
marketId
|
||||||
|
? {
|
||||||
|
data: mockDataMarketState,
|
||||||
|
}
|
||||||
|
: { data: undefined },
|
||||||
useSuccessorMarket: (marketId: string) =>
|
useSuccessorMarket: (marketId: string) =>
|
||||||
marketId
|
marketId
|
||||||
? {
|
? {
|
||||||
@@ -81,30 +88,6 @@ describe('MarketSuccessorBanner', () => {
|
|||||||
});
|
});
|
||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('successor market not in continuous mode', () => {
|
|
||||||
mockDataSuccessorMarket = {
|
|
||||||
...mockDataSuccessorMarket,
|
|
||||||
tradingMode: Types.MarketTradingMode.TRADING_MODE_NO_TRADING,
|
|
||||||
};
|
|
||||||
const { container } = render(<MarketSuccessorBanner market={market} />, {
|
|
||||||
wrapper: MockedProvider,
|
|
||||||
});
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
|
||||||
expect(allUtils.getMarketExpiryDate).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('successor market is not active', () => {
|
|
||||||
mockDataSuccessorMarket = {
|
|
||||||
...mockDataSuccessorMarket,
|
|
||||||
state: Types.MarketState.STATE_PENDING,
|
|
||||||
};
|
|
||||||
const { container } = render(<MarketSuccessorBanner market={market} />, {
|
|
||||||
wrapper: MockedProvider,
|
|
||||||
});
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
|
||||||
expect(allUtils.getMarketExpiryDate).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should be displayed', () => {
|
describe('should be displayed', () => {
|
||||||
@@ -120,6 +103,17 @@ describe('MarketSuccessorBanner', () => {
|
|||||||
).toHaveAttribute('href', '/#/markets/successorMarketID');
|
).toHaveAttribute('href', '/#/markets/successorMarketID');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('no successor market data, market settled', () => {
|
||||||
|
mockDataSuccessorMarket = null;
|
||||||
|
mockDataMarketState = Types.MarketState.STATE_SETTLED;
|
||||||
|
render(<MarketSuccessorBanner market={market} />, {
|
||||||
|
wrapper: MockedProvider,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.getByText('This market has been settled')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('should display optionally successor volume', () => {
|
it('should display optionally successor volume', () => {
|
||||||
mockDataSuccessorMarket = {
|
mockDataSuccessorMarket = {
|
||||||
...mockDataSuccessorMarket,
|
...mockDataSuccessorMarket,
|
||||||
@@ -137,7 +131,9 @@ describe('MarketSuccessorBanner', () => {
|
|||||||
render(<MarketSuccessorBanner market={market} />, {
|
render(<MarketSuccessorBanner market={market} />, {
|
||||||
wrapper: MockedProvider,
|
wrapper: MockedProvider,
|
||||||
});
|
});
|
||||||
expect(screen.getByText('has 101.367 24h vol.')).toBeInTheDocument();
|
expect(
|
||||||
|
screen.getByText('has a 24h trading volume of 101.367')
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display optionally duration', () => {
|
it('should display optionally duration', () => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Market } from '@vegaprotocol/markets';
|
|||||||
import {
|
import {
|
||||||
calcCandleVolume,
|
calcCandleVolume,
|
||||||
useCandles,
|
useCandles,
|
||||||
|
useMarketState,
|
||||||
useSuccessorMarket,
|
useSuccessorMarket,
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +30,9 @@ export const MarketSuccessorBanner = ({
|
|||||||
}: {
|
}: {
|
||||||
market: Market | null;
|
market: Market | null;
|
||||||
}) => {
|
}) => {
|
||||||
const { data: successorData } = useSuccessorMarket(market?.id);
|
const { data: marketState } = useMarketState(market?.id);
|
||||||
|
const isSettled = marketState === Types.MarketState.STATE_SETTLED;
|
||||||
|
const { data: successorData, loading } = useSuccessorMarket(market?.id);
|
||||||
|
|
||||||
const [visible, setVisible] = useState(true);
|
const [visible, setVisible] = useState(true);
|
||||||
|
|
||||||
@@ -45,11 +48,6 @@ export const MarketSuccessorBanner = ({
|
|||||||
? intervalToDuration({ start: new Date(), end: expiry })
|
? intervalToDuration({ start: new Date(), end: expiry })
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const isInContinuesMode =
|
|
||||||
successorData?.state === Types.MarketState.STATE_ACTIVE &&
|
|
||||||
successorData?.tradingMode ===
|
|
||||||
Types.MarketTradingMode.TRADING_MODE_CONTINUOUS;
|
|
||||||
|
|
||||||
const { oneDayCandles } = useCandles({
|
const { oneDayCandles } = useCandles({
|
||||||
marketId: successorData?.id,
|
marketId: successorData?.id,
|
||||||
});
|
});
|
||||||
@@ -66,7 +64,7 @@ export const MarketSuccessorBanner = ({
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (isInContinuesMode && visible) {
|
if (!loading && (isSettled || successorData) && visible) {
|
||||||
return (
|
return (
|
||||||
<NotificationBanner
|
<NotificationBanner
|
||||||
intent={Intent.Primary}
|
intent={Intent.Primary}
|
||||||
@@ -74,34 +72,47 @@ export const MarketSuccessorBanner = ({
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="uppercase mb-1">
|
<div className="uppercase">
|
||||||
{t('This market has been succeeded')}
|
{successorData
|
||||||
</div>
|
? t('This market has been succeeded')
|
||||||
<div>
|
: t('This market has been settled')}
|
||||||
{duration && (
|
|
||||||
<span>
|
|
||||||
{t('This market expires in %s.', [
|
|
||||||
formatDuration(duration, {
|
|
||||||
format: [
|
|
||||||
'years',
|
|
||||||
'months',
|
|
||||||
'weeks',
|
|
||||||
'days',
|
|
||||||
'hours',
|
|
||||||
'minutes',
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
])}
|
|
||||||
</span>
|
|
||||||
)}{' '}
|
|
||||||
{t('The successor market')}{' '}
|
|
||||||
<ExternalLink href={`/#/markets/${successorData?.id}`}>
|
|
||||||
{successorData?.tradableInstrument.instrument.name}
|
|
||||||
</ExternalLink>
|
|
||||||
{successorVolume && (
|
|
||||||
<span> {t('has %s 24h vol.', [successorVolume])}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{(duration || successorData) && (
|
||||||
|
<div className="mt-1">
|
||||||
|
{duration && (
|
||||||
|
<span>
|
||||||
|
{t('This market expires in %s.', [
|
||||||
|
formatDuration(duration, {
|
||||||
|
format: [
|
||||||
|
'years',
|
||||||
|
'months',
|
||||||
|
'weeks',
|
||||||
|
'days',
|
||||||
|
'hours',
|
||||||
|
'minutes',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{successorData && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
{t('The successor market')}
|
||||||
|
{!successorVolume ? ' is ' : ' '}
|
||||||
|
<ExternalLink href={`/#/markets/${successorData?.id}`}>
|
||||||
|
{successorData?.tradableInstrument.instrument.name}
|
||||||
|
</ExternalLink>
|
||||||
|
{successorVolume && (
|
||||||
|
<span>
|
||||||
|
{' '}
|
||||||
|
{t('has a 24h trading volume of %s', [successorVolume])}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</NotificationBanner>
|
</NotificationBanner>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import type { NewMarketSuccessorFieldsFragment } from '@vegaprotocol/proposals';
|
import {
|
||||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
marketViewProposalsDataProvider,
|
||||||
|
type NewMarketSuccessorFieldsFragment,
|
||||||
|
} from '@vegaprotocol/proposals';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Intent,
|
Intent,
|
||||||
@@ -9,17 +12,20 @@ import {
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||||
import * as Types from '@vegaprotocol/types';
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
|
||||||
export const MarketSuccessorProposalBanner = ({
|
export const MarketSuccessorProposalBanner = ({
|
||||||
marketId,
|
marketId,
|
||||||
}: {
|
}: {
|
||||||
marketId?: string;
|
marketId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const proposals = useMarketViewProposals({
|
const { data: proposals } = useDataProvider({
|
||||||
|
dataProvider: marketViewProposalsDataProvider,
|
||||||
skip: !marketId,
|
skip: !marketId,
|
||||||
inState: Types.ProposalState.STATE_OPEN,
|
variables: {
|
||||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
typename: 'NewMarket',
|
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const successors =
|
const successors =
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ const marketMock = {
|
|||||||
},
|
},
|
||||||
} as Market;
|
} as Market;
|
||||||
|
|
||||||
const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: MarketViewProposalsDocument,
|
query: MarketViewProposalsDocument,
|
||||||
variables: { inState: Types.ProposalState.STATE_PASSED },
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_PASSED,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
@@ -31,7 +34,7 @@ const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
|||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
id: 'first-id',
|
id: '1',
|
||||||
state: Types.ProposalState.STATE_PASSED,
|
state: Types.ProposalState.STATE_PASSED,
|
||||||
terms: {
|
terms: {
|
||||||
closingDatetime: '2023-09-27T11:48:18Z',
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
@@ -56,7 +59,7 @@ const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
id: 'second-id',
|
id: '2',
|
||||||
state: Types.ProposalState.STATE_PASSED,
|
state: Types.ProposalState.STATE_PASSED,
|
||||||
terms: {
|
terms: {
|
||||||
closingDatetime: '2023-09-27T11:48:18Z',
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
@@ -84,7 +87,99 @@ const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const mocks: MockedResponse[] = [proposalMock];
|
const openProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketViewProposalsDocument,
|
||||||
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
proposalsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '3',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-01T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-3',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market three name',
|
||||||
|
code: 'Market three',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '4',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-11T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-3',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market three name',
|
||||||
|
code: 'Market three',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '5',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-01T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-4',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market four name',
|
||||||
|
code: 'Market four',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mocks: MockedResponse[] = [passedProposalMock, openProposalMock];
|
||||||
|
|
||||||
describe('MarketTerminationBanner', () => {
|
describe('MarketTerminationBanner', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -108,4 +203,28 @@ describe('MarketTerminationBanner', () => {
|
|||||||
screen.getByTestId('termination-warning-banner-market-1')
|
screen.getByTestId('termination-warning-banner-market-1')
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render link to proposals', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<MarketTerminationBanner market={{ ...marketMock, id: 'market-3' }} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).not.toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
expect(screen.getByText('View proposals')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render link to proposal', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<MarketTerminationBanner market={{ ...marketMock, id: 'market-4' }} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).not.toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
expect(screen.getByText('View proposal')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,67 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { format, formatDuration, intervalToDuration } from 'date-fns';
|
import { format, formatDuration, intervalToDuration } from 'date-fns';
|
||||||
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
ExternalLink,
|
||||||
|
Intent,
|
||||||
|
NotificationBanner,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
|
import { marketViewProposalsDataProvider } from '@vegaprotocol/proposals';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import * as Types from '@vegaprotocol/types';
|
import * as Types from '@vegaprotocol/types';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { getQuoteName } from '@vegaprotocol/markets';
|
import { getQuoteName } from '@vegaprotocol/markets';
|
||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
|
import {
|
||||||
|
DApp,
|
||||||
|
TOKEN_PROPOSAL,
|
||||||
|
TOKEN_PROPOSALS,
|
||||||
|
useLinks,
|
||||||
|
} from '@vegaprotocol/environment';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
|
||||||
|
const filterProposals = (
|
||||||
|
data: MarketViewProposalFieldsFragment[] | null,
|
||||||
|
marketId: string,
|
||||||
|
now: number
|
||||||
|
) =>
|
||||||
|
sortBy(
|
||||||
|
(data || []).filter(
|
||||||
|
(item) =>
|
||||||
|
item.terms.change.__typename === 'UpdateMarketState' &&
|
||||||
|
item.terms.change.market.id === marketId &&
|
||||||
|
item.terms.change.updateType ===
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE &&
|
||||||
|
item.terms.enactmentDatetime &&
|
||||||
|
new Date(item.terms.enactmentDatetime).getTime() > now
|
||||||
|
),
|
||||||
|
(item) => item.terms.enactmentDatetime
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMessageVariables = (proposal: MarketViewProposalFieldsFragment) => {
|
||||||
|
const enactmentDatetime = new Date(proposal.terms.enactmentDatetime);
|
||||||
|
const date = format(enactmentDatetime, 'dd MMMM');
|
||||||
|
const duration = formatDuration(
|
||||||
|
intervalToDuration({
|
||||||
|
start: new Date(),
|
||||||
|
end: enactmentDatetime,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
format: ['days', 'hours'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const price =
|
||||||
|
proposal.terms.change.__typename === 'UpdateMarketState'
|
||||||
|
? proposal.terms.change.price
|
||||||
|
: '';
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
duration,
|
||||||
|
price,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const MarketTerminationBanner = ({
|
export const MarketTerminationBanner = ({
|
||||||
market,
|
market,
|
||||||
@@ -15,57 +70,55 @@ export const MarketTerminationBanner = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [visible, setVisible] = useState(true);
|
const [visible, setVisible] = useState(true);
|
||||||
const skip = !market || !visible;
|
const skip = !market || !visible;
|
||||||
const proposalsData = useMarketViewProposals({
|
const { data: passedProposalsData } = useDataProvider({
|
||||||
|
dataProvider: marketViewProposalsDataProvider,
|
||||||
skip,
|
skip,
|
||||||
inState: Types.ProposalState.STATE_PASSED,
|
variables: {
|
||||||
typename: 'UpdateMarketState',
|
inState: Types.ProposalState.STATE_PASSED,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: openProposalsData } = useDataProvider({
|
||||||
|
dataProvider: marketViewProposalsDataProvider,
|
||||||
|
skip,
|
||||||
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const governanceLink = useLinks(DApp.Governance);
|
||||||
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
const marketFound = (proposalsData || []).find(
|
const now = Date.now();
|
||||||
(item) =>
|
const passedProposals = filterProposals(passedProposalsData, market.id, now);
|
||||||
item.terms.change.__typename === 'UpdateMarketState' &&
|
const openProposals = filterProposals(openProposalsData, market.id, now);
|
||||||
item.terms.change.market.id === market.id &&
|
|
||||||
item.terms.change.updateType ===
|
|
||||||
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE &&
|
|
||||||
item.state === Types.ProposalState.STATE_PASSED // subscription doesn't have state parameter
|
|
||||||
);
|
|
||||||
|
|
||||||
const enactmentDatetime = new Date(marketFound?.terms.enactmentDatetime);
|
const name = market.tradableInstrument.instrument.code;
|
||||||
const name =
|
if (!passedProposals.length && !openProposals.length) {
|
||||||
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
return null;
|
||||||
? marketFound.terms.change.market.tradableInstrument.instrument.code
|
}
|
||||||
: '';
|
|
||||||
|
|
||||||
if (name && enactmentDatetime.getTime() > Date.now()) {
|
const assetSymbol = getQuoteName(market);
|
||||||
const dayMonthDate = format(enactmentDatetime, 'dd MMMM');
|
const proposalLink =
|
||||||
const duration = intervalToDuration({
|
!passedProposals.length && openProposals[0]?.id
|
||||||
start: new Date(),
|
? governanceLink(TOKEN_PROPOSAL.replace(':id', openProposals[0]?.id))
|
||||||
end: enactmentDatetime,
|
: undefined;
|
||||||
});
|
const proposalsLink =
|
||||||
const formattedDuration = formatDuration(duration, {
|
openProposals.length > 1 ? governanceLink(TOKEN_PROPOSALS) : undefined;
|
||||||
format: ['days', 'hours'],
|
let content: ReactNode;
|
||||||
});
|
if (passedProposals.length) {
|
||||||
const price =
|
const { date, duration, price } = getMessageVariables(passedProposals[0]);
|
||||||
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
content = (
|
||||||
? marketFound.terms.change.price
|
<>
|
||||||
: '';
|
|
||||||
const assetSymbol = getQuoteName(market);
|
|
||||||
return (
|
|
||||||
<NotificationBanner
|
|
||||||
intent={Intent.Warning}
|
|
||||||
onClose={() => {
|
|
||||||
setVisible(false);
|
|
||||||
}}
|
|
||||||
data-testid={`termination-warning-banner-${market.id}`}
|
|
||||||
>
|
|
||||||
<div className="uppercase mb-1">
|
<div className="uppercase mb-1">
|
||||||
{t('Trading on Market %s will stop on %s', [name, dayMonthDate])}
|
{t('Trading on Market %s will stop on %s', [name, date])}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{t(
|
{t(
|
||||||
'You will no longer be able to hold a position on this market when it closes in %s.',
|
'You will no longer be able to hold a position on this market when it closes in %s.',
|
||||||
[formattedDuration]
|
[duration]
|
||||||
)}{' '}
|
)}{' '}
|
||||||
{price &&
|
{price &&
|
||||||
assetSymbol &&
|
assetSymbol &&
|
||||||
@@ -74,8 +127,57 @@ export const MarketTerminationBanner = ({
|
|||||||
assetSymbol,
|
assetSymbol,
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
</NotificationBanner>
|
</>
|
||||||
|
);
|
||||||
|
} else if (openProposals.length > 1) {
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<div className="uppercase mb-1">
|
||||||
|
{t(
|
||||||
|
'Trading on Market %s may stop. There are open proposals to close this market',
|
||||||
|
[name]
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ExternalLink href={proposalsLink}>
|
||||||
|
{t('View proposals')}
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const { date, price } = getMessageVariables(openProposals[0]);
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<div className="uppercase mb-1">
|
||||||
|
{t(
|
||||||
|
'Trading on Market %s may stop on %s. There is open proposal to close this market.',
|
||||||
|
[name, date]
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{price &&
|
||||||
|
assetSymbol &&
|
||||||
|
t('Proposed final price is %s %s.', [
|
||||||
|
addDecimalsFormatNumber(price, market.decimalPlaces),
|
||||||
|
assetSymbol,
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return (
|
||||||
|
<NotificationBanner
|
||||||
|
intent={openProposals.length ? Intent.Warning : Intent.Info}
|
||||||
|
onClose={() => {
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
data-testid={`termination-warning-banner-${market.id}`}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</NotificationBanner>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Popover, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
import { Popover, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Header, HeaderTitle } from '../header';
|
import { Header, HeaderTitle } from '../header';
|
||||||
import { useParams } from 'react-router-dom';
|
import { Route, Routes, useParams } from 'react-router-dom';
|
||||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||||
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||||
@@ -19,36 +19,44 @@ export const MarketHeader = () => {
|
|||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header
|
<Routes>
|
||||||
title={
|
<Route
|
||||||
<Popover
|
path=":marketId"
|
||||||
open={open}
|
element={
|
||||||
onChange={setOpen}
|
<Header
|
||||||
trigger={
|
title={
|
||||||
<HeaderTitle>
|
<Popover
|
||||||
<span>
|
open={open}
|
||||||
{data.tradableInstrument.instrument.code}
|
onChange={setOpen}
|
||||||
<span className="ml-2 text-xs uppercase text-muted">
|
trigger={
|
||||||
{' '}
|
<HeaderTitle>
|
||||||
{data.tradableInstrument.instrument.product.__typename &&
|
<span>
|
||||||
ProductTypeShortName[
|
{data.tradableInstrument.instrument.code}
|
||||||
data.tradableInstrument.instrument.product.__typename
|
<span className="ml-1 text-xs uppercase text-muted">
|
||||||
]}{' '}
|
{data.tradableInstrument.instrument.product
|
||||||
</span>
|
.__typename &&
|
||||||
</span>
|
ProductTypeShortName[
|
||||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
data.tradableInstrument.instrument.product
|
||||||
</HeaderTitle>
|
.__typename
|
||||||
}
|
]}
|
||||||
alignOffset={-10}
|
</span>
|
||||||
>
|
</span>
|
||||||
<MarketSelector
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||||
currentMarketId={marketId}
|
</HeaderTitle>
|
||||||
onSelect={() => setOpen(false)}
|
}
|
||||||
/>
|
alignOffset={-10}
|
||||||
</Popover>
|
>
|
||||||
}
|
<MarketSelector
|
||||||
>
|
currentMarketId={marketId}
|
||||||
<MarketHeaderStats market={data} />
|
onSelect={() => setOpen(false)}
|
||||||
</Header>
|
/>
|
||||||
|
</Popover>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MarketHeaderStats market={data} />
|
||||||
|
</Header>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const MarketData = ({
|
|||||||
variables: {
|
variables: {
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
},
|
},
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
});
|
});
|
||||||
|
|
||||||
const marketData = data?.marketsData[0];
|
const marketData = data?.marketsData[0];
|
||||||
@@ -70,6 +71,8 @@ const MarketData = ({
|
|||||||
|
|
||||||
const marketTradingMode = marketData
|
const marketTradingMode = marketData
|
||||||
? marketData.marketTradingMode
|
? marketData.marketTradingMode
|
||||||
|
: market.data
|
||||||
|
? market.data.marketTradingMode
|
||||||
: market.tradingMode;
|
: market.tradingMode;
|
||||||
|
|
||||||
const mode = [
|
const mode = [
|
||||||
@@ -95,7 +98,7 @@ const MarketData = ({
|
|||||||
<>
|
<>
|
||||||
<div className="w-2/5" role="gridcell">
|
<div className="w-2/5" role="gridcell">
|
||||||
<h3 className="flex items-baseline">
|
<h3 className="flex items-baseline">
|
||||||
<span className="text-sm lg:text-base text-ellipsis whitespace-nowrap overflow-hidden">
|
<span className="overflow-hidden text-sm lg:text-base text-ellipsis whitespace-nowrap">
|
||||||
{market.tradableInstrument.instrument.code}
|
{market.tradableInstrument.instrument.code}
|
||||||
</span>
|
</span>
|
||||||
{allProducts && productType && (
|
{allProducts && productType && (
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { MarketSelector } from './market-selector';
|
import { MarketSelector } from './market-selector';
|
||||||
import { useMarketList } from '@vegaprotocol/markets';
|
import { useMarketList } from '@vegaprotocol/markets';
|
||||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
import {
|
||||||
|
createMarketFragment,
|
||||||
|
createMarketsDataFragment,
|
||||||
|
} from '@vegaprotocol/mock';
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@@ -36,6 +39,10 @@ describe('MarketSelector', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'a',
|
code: 'a',
|
||||||
@@ -56,7 +63,10 @@ describe('MarketSelector', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
state: MarketState.STATE_SUSPENDED,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_SUSPENDED,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'b',
|
code: 'b',
|
||||||
@@ -77,7 +87,10 @@ describe('MarketSelector', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
state: MarketState.STATE_CLOSED,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_CLOSED,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -91,7 +104,10 @@ describe('MarketSelector', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
state: MarketState.STATE_ACTIVE,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'c',
|
code: 'c',
|
||||||
@@ -112,6 +128,10 @@ describe('MarketSelector', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-4',
|
id: 'market-4',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'cd',
|
code: 'cd',
|
||||||
@@ -132,7 +152,10 @@ describe('MarketSelector', () => {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const activeMarkets = markets.filter((m) => isMarketActive(m.state));
|
const activeMarkets = markets.filter((m) =>
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
isMarketActive(m.data.marketState)
|
||||||
|
);
|
||||||
mockUseMarketList.mockReturnValue({
|
mockUseMarketList.mockReturnValue({
|
||||||
data: markets,
|
data: markets,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import { useMarketSelectorList } from './use-market-selector-list';
|
|||||||
import { isMarketActive } from '../../lib/utils';
|
import { isMarketActive } from '../../lib/utils';
|
||||||
import { Product } from './product-selector';
|
import { Product } from './product-selector';
|
||||||
import { Sort } from './sort-dropdown';
|
import { Sort } from './sort-dropdown';
|
||||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
import {
|
||||||
|
createMarketFragment,
|
||||||
|
createMarketsDataFragment,
|
||||||
|
} from '@vegaprotocol/mock';
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
import { useMarketList } from '@vegaprotocol/markets';
|
import { useMarketList } from '@vegaprotocol/markets';
|
||||||
import type { Filter } from './market-selector';
|
import type { Filter } from './market-selector';
|
||||||
@@ -31,22 +34,40 @@ describe('useMarketSelectorList', () => {
|
|||||||
|
|
||||||
it('returns all markets active and suspended markets', () => {
|
it('returns all markets active and suspended markets', () => {
|
||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({ id: 'market-0' }),
|
createMarketFragment({
|
||||||
|
id: 'market-0',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
state: MarketState.STATE_SUSPENDED,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_SUSPENDED,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
state: MarketState.STATE_CLOSED,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_CLOSED,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
state: MarketState.STATE_CLOSED,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_CLOSED,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-4',
|
id: 'market-4',
|
||||||
state: MarketState.STATE_PENDING,
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_PENDING,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
mockUseMarketList.mockReturnValue({
|
mockUseMarketList.mockReturnValue({
|
||||||
@@ -56,7 +77,8 @@ describe('useMarketSelectorList', () => {
|
|||||||
});
|
});
|
||||||
const { result } = setup();
|
const { result } = setup();
|
||||||
const expectedFilteredMarkets = markets.filter((m) =>
|
const expectedFilteredMarkets = markets.filter((m) =>
|
||||||
isMarketActive(m.state)
|
// @ts-ignore candles get joined outside this type
|
||||||
|
isMarketActive(m.data.marketState)
|
||||||
);
|
);
|
||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
data: markets,
|
data: markets,
|
||||||
@@ -70,6 +92,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -90,6 +116,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
// }),
|
// }),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -135,6 +165,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -148,6 +182,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -161,6 +199,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -174,6 +216,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
product: {
|
product: {
|
||||||
@@ -238,6 +284,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'abc',
|
code: 'abc',
|
||||||
@@ -247,6 +297,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'def',
|
code: 'def',
|
||||||
@@ -256,6 +310,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'defg',
|
code: 'defg',
|
||||||
@@ -265,6 +323,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
|
// @ts-ignore candles get joined outside this type
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
code: 'ggg',
|
code: 'ggg',
|
||||||
@@ -333,11 +395,11 @@ describe('useMarketSelectorList', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
state: MarketState.STATE_ACTIVE,
|
|
||||||
// @ts-ignore data not on fragment
|
// @ts-ignore data not on fragment
|
||||||
data: {
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
markPrice: '1',
|
markPrice: '1',
|
||||||
},
|
}),
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -349,9 +411,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
state: MarketState.STATE_ACTIVE,
|
state: MarketState.STATE_ACTIVE,
|
||||||
// @ts-ignore data not on fragment
|
// @ts-ignore data not on fragment
|
||||||
data: {
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
markPrice: '1',
|
markPrice: '1',
|
||||||
},
|
}),
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -363,9 +426,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
state: MarketState.STATE_ACTIVE,
|
state: MarketState.STATE_ACTIVE,
|
||||||
// @ts-ignore data not on fragment
|
// @ts-ignore data not on fragment
|
||||||
data: {
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
markPrice: '1',
|
markPrice: '1',
|
||||||
},
|
}),
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -377,9 +441,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
state: MarketState.STATE_ACTIVE,
|
state: MarketState.STATE_ACTIVE,
|
||||||
// @ts-ignore data not on fragment
|
// @ts-ignore data not on fragment
|
||||||
data: {
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
markPrice: '1',
|
markPrice: '1',
|
||||||
},
|
}),
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -414,6 +479,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
// @ts-ignore actual fragment doesn't contain candles and is joined later
|
// @ts-ignore actual fragment doesn't contain candles and is joined later
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -426,6 +495,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
// @ts-ignore actual fragment doesn't contain candles and is joined later
|
// @ts-ignore actual fragment doesn't contain candles and is joined later
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -438,6 +511,10 @@ describe('useMarketSelectorList', () => {
|
|||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
// @ts-ignore actual fragment doesn't contain candles and is joined later
|
// @ts-ignore actual fragment doesn't contain candles and is joined later
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@@ -482,18 +559,30 @@ describe('useMarketSelectorList', () => {
|
|||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
marketTimestamps: {
|
marketTimestamps: {
|
||||||
open: subDays(new Date(), 3).toISOString(),
|
open: subDays(new Date(), 3).toISOString(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
marketTimestamps: {
|
marketTimestamps: {
|
||||||
open: subDays(new Date(), 1).toISOString(),
|
open: subDays(new Date(), 1).toISOString(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
marketState: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
marketTimestamps: {
|
marketTimestamps: {
|
||||||
open: subDays(new Date(), 2).toISOString(),
|
open: subDays(new Date(), 2).toISOString(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,8 +22,12 @@ export const useMarketSelectorList = ({
|
|||||||
const markets = useMemo(() => {
|
const markets = useMemo(() => {
|
||||||
if (!data?.length) return [];
|
if (!data?.length) return [];
|
||||||
const markets = data
|
const markets = data
|
||||||
// only active
|
// show only active markets, using m.data.marketState as this will be
|
||||||
.filter((m) => isMarketActive(m.state))
|
// data that will get refreshed when calling reload
|
||||||
|
.filter((m) => {
|
||||||
|
if (!m.data) return false;
|
||||||
|
return isMarketActive(m.data.marketState);
|
||||||
|
})
|
||||||
// only selected product type
|
// only selected product type
|
||||||
.filter((m) => {
|
.filter((m) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe('Navbar', () => {
|
|||||||
|
|
||||||
const expectedLinks = [
|
const expectedLinks = [
|
||||||
['/', ''],
|
['/', ''],
|
||||||
['/markets/all', 'Markets'],
|
['/markets', 'Markets'],
|
||||||
[`/markets/${marketId}`, 'Trading'],
|
[`/markets/${marketId}`, 'Trading'],
|
||||||
['/portfolio', 'Portfolio'],
|
['/portfolio', 'Portfolio'],
|
||||||
['/referrals', 'Referrals'],
|
['/referrals', 'Referrals'],
|
||||||
@@ -80,7 +80,7 @@ describe('Navbar', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Markets page route should not match empty market page', () => {
|
it('Markets page route should not match empty market page', () => {
|
||||||
renderComponent(['/markets/all']);
|
renderComponent(['/markets']);
|
||||||
expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active');
|
expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active');
|
||||||
expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass(
|
expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass(
|
||||||
'active'
|
'active'
|
||||||
@@ -96,7 +96,7 @@ describe('Navbar', () => {
|
|||||||
const menu = within(menuEl);
|
const menu = within(menuEl);
|
||||||
|
|
||||||
const expectedLinks = [
|
const expectedLinks = [
|
||||||
['/markets/all', 'Markets'],
|
['/markets', 'Markets'],
|
||||||
[`/markets/${marketId}`, 'Trading'],
|
[`/markets/${marketId}`, 'Trading'],
|
||||||
['/portfolio', 'Portfolio'],
|
['/portfolio', 'Portfolio'],
|
||||||
['/referrals', 'Referrals'],
|
['/referrals', 'Referrals'],
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ const NavbarLink = ({
|
|||||||
<N.Link asChild={true}>
|
<N.Link asChild={true}>
|
||||||
<NavLink
|
<NavLink
|
||||||
to={to}
|
to={to}
|
||||||
|
end={true}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'block lg:flex lg:h-full flex-col justify-center',
|
'block lg:flex lg:h-full flex-col justify-center',
|
||||||
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||||
|
|||||||
@@ -3,14 +3,25 @@ import { Filter, OrderListManager } from '@vegaprotocol/orders';
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import {
|
import { useNavigateWithMeta } from '../../lib/hooks/use-market-click-handler';
|
||||||
useMarketClickHandler,
|
|
||||||
useMarketLiquidityClickHandler,
|
|
||||||
} from '../../lib/hooks/use-market-click-handler';
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import type { DataGridStore } from '../../stores/datagrid-store-slice';
|
import type { DataGridStore } from '../../stores/datagrid-store-slice';
|
||||||
import { OrderStatus } from '@vegaprotocol/types';
|
import { OrderStatus } from '@vegaprotocol/types';
|
||||||
|
import { Links } from '../../lib/links';
|
||||||
|
|
||||||
|
const resolveNoRowsMessage = (filter?: Filter) => {
|
||||||
|
switch (filter) {
|
||||||
|
case Filter.Open:
|
||||||
|
return t('No open orders');
|
||||||
|
case Filter.Closed:
|
||||||
|
return t('No closed orders');
|
||||||
|
case Filter.Rejected:
|
||||||
|
return t('No rejected orders');
|
||||||
|
default:
|
||||||
|
return t('No orders');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const FilterStatusValue = {
|
export const FilterStatusValue = {
|
||||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||||
@@ -32,8 +43,7 @@ const AUTO_SIZE_COLUMNS = ['instrument-code'];
|
|||||||
|
|
||||||
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
||||||
const { pubKey, isReadOnly } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const onMarketClick = useMarketClickHandler(true);
|
const navigate = useNavigateWithMeta();
|
||||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
|
||||||
const { gridState, updateGridState } = useOrderListGridState(filter);
|
const { gridState, updateGridState } = useOrderListGridState(filter);
|
||||||
const gridStoreCallbacks = useDataGridEvents(
|
const gridStoreCallbacks = useDataGridEvents(
|
||||||
gridState,
|
gridState,
|
||||||
@@ -46,15 +56,21 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
|||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||||
}
|
}
|
||||||
|
const noRowsMessage = resolveNoRowsMessage(filter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrderListManager
|
<OrderListManager
|
||||||
partyId={pubKey}
|
partyId={pubKey}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onMarketClick={onMarketClick}
|
onMarketClick={(marketId, metaKey) => {
|
||||||
onOrderTypeClick={onOrderTypeClick}
|
navigate(Links.MARKET(marketId), metaKey);
|
||||||
|
}}
|
||||||
|
onOrderTypeClick={(marketId, metaKey) =>
|
||||||
|
navigate(Links.LIQUIDITY(marketId), metaKey)
|
||||||
|
}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
gridProps={gridStoreCallbacks}
|
gridProps={gridStoreCallbacks}
|
||||||
|
noRowsMessage={noRowsMessage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
|
||||||
|
|
||||||
export const Preloader = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<style>
|
|
||||||
{`
|
|
||||||
body{
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
html.dark body{
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
<div className="pre-loader">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Preloader;
|
|
||||||
@@ -38,79 +38,68 @@ const walletContext = {
|
|||||||
} as VegaWalletContextShape;
|
} as VegaWalletContextShape;
|
||||||
|
|
||||||
describe('Sidebar', () => {
|
describe('Sidebar', () => {
|
||||||
it.each(['/markets/all', '/portfolio'])(
|
beforeEach(() => {
|
||||||
'does not render ticket and info',
|
useSidebar.setState({ views: {} });
|
||||||
(path) => {
|
});
|
||||||
render(
|
|
||||||
<VegaWalletContext.Provider value={walletContext}>
|
|
||||||
<MemoryRouter initialEntries={[path]}>
|
|
||||||
<Sidebar />
|
|
||||||
</MemoryRouter>
|
|
||||||
</VegaWalletContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
it('renders options prop', () => {
|
||||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(ViewType.Withdraw)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('node-health')).toBeInTheDocument();
|
|
||||||
|
|
||||||
// no order or info sidebars
|
|
||||||
expect(screen.queryByTestId(ViewType.Order)).not.toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId(ViewType.Info)).not.toBeInTheDocument();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
it('renders ticket and info on market pages', () => {
|
|
||||||
render(
|
render(
|
||||||
<VegaWalletContext.Provider value={walletContext}>
|
<VegaWalletContext.Provider value={walletContext}>
|
||||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
<MemoryRouter>
|
||||||
<Sidebar />
|
<Sidebar options={<div data-testid="options" />} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(ViewType.Withdraw)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('node-health')).toBeInTheDocument();
|
expect(screen.getByTestId('node-health')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('options')).toBeInTheDocument();
|
||||||
// order and info sidebars are shown
|
|
||||||
expect(screen.getByTestId(ViewType.Order)).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(ViewType.Info)).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders selected state', async () => {
|
it('renders selected state', async () => {
|
||||||
|
const routeId = '/markets/ABC';
|
||||||
render(
|
render(
|
||||||
<VegaWalletContext.Provider value={walletContext}>
|
<VegaWalletContext.Provider value={walletContext}>
|
||||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
<MemoryRouter initialEntries={[routeId]}>
|
||||||
<Sidebar />
|
<Sidebar
|
||||||
|
options={
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.Deposit}
|
||||||
|
icon={VegaIconNames.DEPOSIT}
|
||||||
|
tooltip="Deposit"
|
||||||
|
routeId={'/markets/:marketId'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||||
const orderButton = screen.getByTestId(ViewType.Order);
|
const depositButton = screen.getByTestId(ViewType.Deposit);
|
||||||
|
|
||||||
// select settings first
|
// select settings first
|
||||||
await userEvent.click(settingsButton);
|
await userEvent.click(settingsButton);
|
||||||
expect(settingsButton).toHaveClass('bg-vega-yellow text-black');
|
expect(settingsButton).toHaveClass('bg-vega-yellow text-black');
|
||||||
|
|
||||||
// switch to order
|
// switch to deposit
|
||||||
await userEvent.click(orderButton);
|
await userEvent.click(depositButton);
|
||||||
|
|
||||||
expect(settingsButton).not.toHaveClass('bg-vega-yellow text-black');
|
expect(settingsButton).not.toHaveClass('bg-vega-yellow text-black');
|
||||||
expect(orderButton).toHaveClass('bg-vega-yellow text-black');
|
expect(depositButton).toHaveClass('bg-vega-yellow text-black');
|
||||||
|
|
||||||
// close order
|
// close order
|
||||||
await userEvent.click(orderButton);
|
await userEvent.click(depositButton);
|
||||||
expect(orderButton).not.toHaveClass('bg-vega-yellow text-black');
|
expect(depositButton).not.toHaveClass('bg-vega-yellow text-black');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('SidebarContent', () => {
|
describe('SidebarContent', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useSidebar.setState({ views: {} });
|
||||||
|
});
|
||||||
|
|
||||||
it('renders the correct content', () => {
|
it('renders the correct content', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<VegaWalletContext.Provider value={walletContext}>
|
<VegaWalletContext.Provider value={walletContext}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Route, Routes, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { TransferContainer } from '@vegaprotocol/accounts';
|
import { TransferContainer } from '@vegaprotocol/accounts';
|
||||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||||
@@ -50,108 +50,14 @@ type SidebarView =
|
|||||||
type: ViewType.Settings;
|
type: ViewType.Settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MarketSidebarButtons = () => {
|
export const Sidebar = ({ options }: { options?: ReactNode }) => {
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SidebarButton
|
|
||||||
view={ViewType.Order}
|
|
||||||
icon={VegaIconNames.TICKET}
|
|
||||||
tooltip={t('Order')}
|
|
||||||
routeId={currentRouteId}
|
|
||||||
/>
|
|
||||||
<SidebarButton
|
|
||||||
view={ViewType.Info}
|
|
||||||
icon={VegaIconNames.BREAKDOWN}
|
|
||||||
tooltip={t('Market specification')}
|
|
||||||
routeId={currentRouteId}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AssetSidebarButtons = () => {
|
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SidebarButton
|
|
||||||
view={ViewType.Deposit}
|
|
||||||
icon={VegaIconNames.DEPOSIT}
|
|
||||||
tooltip={t('Deposit')}
|
|
||||||
routeId={currentRouteId}
|
|
||||||
/>
|
|
||||||
<SidebarButton
|
|
||||||
view={ViewType.Withdraw}
|
|
||||||
icon={VegaIconNames.WITHDRAW}
|
|
||||||
tooltip={t('Withdraw')}
|
|
||||||
routeId={currentRouteId}
|
|
||||||
/>
|
|
||||||
<SidebarButton
|
|
||||||
view={ViewType.Transfer}
|
|
||||||
icon={VegaIconNames.TRANSFER}
|
|
||||||
tooltip={t('Transfer')}
|
|
||||||
routeId={currentRouteId}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Sidebar = () => {
|
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||||
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
||||||
const { pubKeys } = useVegaWallet();
|
const { pubKeys } = useVegaWallet();
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
|
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
|
||||||
<nav className={navClasses}>
|
{options && <nav className={navClasses}>{options}</nav>}
|
||||||
<Routes>
|
|
||||||
<Route path="markets/all" element={<AssetSidebarButtons />} />
|
|
||||||
<Route path="portfolio">
|
|
||||||
<Route
|
|
||||||
// Show deposit/withdraw/transfer sidebar options only on portflio dashboard (index)
|
|
||||||
index={true}
|
|
||||||
element={<AssetSidebarButtons />}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
<Route
|
|
||||||
path="markets/:marketId"
|
|
||||||
element={
|
|
||||||
<>
|
|
||||||
<AssetSidebarButtons />
|
|
||||||
<SidebarDivider />
|
|
||||||
<MarketSidebarButtons />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="liquidity/:marketId"
|
|
||||||
element={
|
|
||||||
<>
|
|
||||||
<AssetSidebarButtons />
|
|
||||||
<SidebarDivider />
|
|
||||||
<MarketSidebarButtons />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="markets/all/closed/:marketId"
|
|
||||||
element={
|
|
||||||
<>
|
|
||||||
<AssetSidebarButtons />
|
|
||||||
<SidebarDivider />
|
|
||||||
<SidebarButton
|
|
||||||
view={ViewType.Info}
|
|
||||||
icon={VegaIconNames.BREAKDOWN}
|
|
||||||
tooltip={t('Market specification')}
|
|
||||||
routeId={currentRouteId}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</nav>
|
|
||||||
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
view={ViewType.ViewAs}
|
view={ViewType.ViewAs}
|
||||||
@@ -234,7 +140,7 @@ export const SidebarButton = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarDivider = () => {
|
export const SidebarDivider = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"
|
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { TelemetryApproval } from './telemetry-approval';
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
|
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
|
||||||
|
|
||||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_toast_id';
|
||||||
|
|
||||||
export const Telemetry = () => {
|
export const Telemetry = () => {
|
||||||
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
|
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { Links } from '../../lib/links';
|
import { Links } from '../../lib/links';
|
||||||
|
|
||||||
export const useMarketClickHandler = (replace = false) => {
|
export const useMarketClickHandler = (replace = false) => {
|
||||||
@@ -15,17 +14,10 @@ export const useMarketClickHandler = (replace = false) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useMarketLiquidityClickHandler = () => {
|
export const useNavigateWithMeta = (replace = false) => {
|
||||||
return useCallback((selectedId: string, metaKey?: boolean) => {
|
|
||||||
window.open(`/#/liquidity/${selectedId}`, metaKey ? '_blank' : '_self');
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useClosedMarketClickHandler = (replace = false) => {
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (selectedId: string, metaKey?: boolean) => {
|
return (link: string, metaKey?: boolean) => {
|
||||||
const link = Links.CLOSED_MARKETS(selectedId);
|
|
||||||
if (metaKey) {
|
if (metaKey) {
|
||||||
window.open(`/#${link}`, '_blank');
|
window.open(`/#${link}`, '_blank');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import trimEnd from 'lodash/trimEnd';
|
|||||||
// href creation
|
// href creation
|
||||||
export const Routes = {
|
export const Routes = {
|
||||||
HOME: '/',
|
HOME: '/',
|
||||||
MARKETS: '/markets/all',
|
MARKETS: '/markets',
|
||||||
CLOSED_MARKETS: '/markets/all/closed/:marketId',
|
|
||||||
MARKET: '/markets/:marketId',
|
MARKET: '/markets/:marketId',
|
||||||
LIQUIDITY: '/liquidity/:marketId',
|
LIQUIDITY: '/liquidity/:marketId',
|
||||||
PORTFOLIO: '/portfolio',
|
PORTFOLIO: '/portfolio',
|
||||||
@@ -29,8 +28,6 @@ export const Links: ConsoleLinks = {
|
|||||||
MARKET: (marketId: string) =>
|
MARKET: (marketId: string) =>
|
||||||
trimEnd(Routes.MARKET.replace(':marketId', marketId)),
|
trimEnd(Routes.MARKET.replace(':marketId', marketId)),
|
||||||
MARKETS: () => Routes.MARKETS,
|
MARKETS: () => Routes.MARKETS,
|
||||||
CLOSED_MARKETS: (marketId: string) =>
|
|
||||||
trimEnd(Routes.CLOSED_MARKETS.replace(':marketId', marketId)),
|
|
||||||
PORTFOLIO: () => Routes.PORTFOLIO,
|
PORTFOLIO: () => Routes.PORTFOLIO,
|
||||||
LIQUIDITY: (marketId: string) =>
|
LIQUIDITY: (marketId: string) =>
|
||||||
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
|
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
|
||||||
useEagerConnect as useVegaEagerConnect,
|
|
||||||
useVegaWallet,
|
|
||||||
} from '@vegaprotocol/wallet';
|
|
||||||
import {
|
|
||||||
useVegaTransactionManager,
|
|
||||||
useVegaTransactionUpdater,
|
|
||||||
} from '@vegaprotocol/web3';
|
|
||||||
import {
|
|
||||||
useEagerConnect as useEthereumEagerConnect,
|
|
||||||
useEthTransactionManager,
|
|
||||||
useEthTransactionUpdater,
|
|
||||||
useEthWithdrawApprovalsManager,
|
|
||||||
} from '@vegaprotocol/web3';
|
|
||||||
import {
|
import {
|
||||||
envTriggerMapping,
|
envTriggerMapping,
|
||||||
Networks,
|
Networks,
|
||||||
@@ -25,21 +11,11 @@ import {
|
|||||||
useNodeSwitcherStore,
|
useNodeSwitcherStore,
|
||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
import { useGlobalStore, usePageTitleStore } from '../stores';
|
import { usePageTitleStore } from '../stores';
|
||||||
import DialogsContainer from './dialogs-container';
|
import DialogsContainer from './dialogs-container';
|
||||||
import ToastsManager from './toasts-manager';
|
import ToastsManager from './toasts-manager';
|
||||||
import {
|
import { HashRouter, useLocation, Route, Routes } from 'react-router-dom';
|
||||||
HashRouter,
|
import { Bootstrapper } from '../components/bootstrapper';
|
||||||
useLocation,
|
|
||||||
Route,
|
|
||||||
Routes,
|
|
||||||
useSearchParams,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import { Connectors } from '../lib/vega-connectors';
|
|
||||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
|
||||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
|
||||||
import { AnnouncementBanner } from '../components/banner';
|
import { AnnouncementBanner } from '../components/banner';
|
||||||
import { Navbar } from '../components/navbar';
|
import { Navbar } from '../components/navbar';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -52,6 +28,10 @@ import { ViewingBanner } from '../components/viewing-banner';
|
|||||||
import { NavHeader } from '../components/navbar/nav-header';
|
import { NavHeader } from '../components/navbar/nav-header';
|
||||||
import { Telemetry } from '../components/telemetry';
|
import { Telemetry } from '../components/telemetry';
|
||||||
import { Routes as AppRoutes } from '../lib/links';
|
import { Routes as AppRoutes } from '../lib/links';
|
||||||
|
import { SSRLoader } from './ssr-loader';
|
||||||
|
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
|
||||||
|
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
|
||||||
|
import { TransactionHandlers } from './transaction-handlers';
|
||||||
|
|
||||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||||
|
|
||||||
@@ -76,15 +56,6 @@ const Title = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const InitializeHandlers = () => {
|
|
||||||
useVegaTransactionManager();
|
|
||||||
useVegaTransactionUpdater();
|
|
||||||
useEthTransactionManager();
|
|
||||||
useEthTransactionUpdater();
|
|
||||||
useEthWithdrawApprovalsManager();
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function AppBody({ Component }: AppProps) {
|
function AppBody({ Component }: AppProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
@@ -124,9 +95,9 @@ function AppBody({ Component }: AppProps) {
|
|||||||
</div>
|
</div>
|
||||||
<DialogsContainer />
|
<DialogsContainer />
|
||||||
<ToastsManager />
|
<ToastsManager />
|
||||||
<InitializeHandlers />
|
<TransactionHandlers />
|
||||||
<MaybeConnectEagerly />
|
<MaybeConnectEagerly />
|
||||||
<PartyData />
|
<PartyActiveOrdersHandler />
|
||||||
<Telemetry />
|
<Telemetry />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -139,55 +110,26 @@ function VegaTradingApp(props: AppProps) {
|
|||||||
store.setDialogOpen,
|
store.setDialogOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Start validation of env vars and determine VEGA_URL
|
||||||
useInitializeEnv();
|
useInitializeEnv();
|
||||||
|
|
||||||
// Prevent HashRouter from being server side rendered as it
|
// Prevent HashRouter from being server side rendered as it
|
||||||
// relies on presence of document object
|
// relies on presence of document object
|
||||||
|
//
|
||||||
|
// This is the last point at which we get pregenerated
|
||||||
|
// HTML so render a ssr friendly loader component
|
||||||
if (status === 'default') {
|
if (status === 'default') {
|
||||||
return <DynamicLoader />;
|
return <SSRLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<AppLoader>
|
<Bootstrapper>
|
||||||
<AppBody {...props} />
|
<AppBody {...props} />
|
||||||
</AppLoader>
|
</Bootstrapper>
|
||||||
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
|
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VegaTradingApp;
|
export default VegaTradingApp;
|
||||||
|
|
||||||
const PartyData = () => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
|
||||||
const variables = { partyId: pubKey || '' };
|
|
||||||
const skip = !pubKey;
|
|
||||||
useDataProvider({
|
|
||||||
dataProvider: activeOrdersProvider,
|
|
||||||
variables,
|
|
||||||
skip,
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const MaybeConnectEagerly = () => {
|
|
||||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
|
||||||
const update = useGlobalStore((store) => store.update);
|
|
||||||
const eagerConnecting = useVegaEagerConnect(Connectors);
|
|
||||||
const [isTelemetryApproved] = useTelemetryApproval();
|
|
||||||
useEthereumEagerConnect(
|
|
||||||
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
|
||||||
);
|
|
||||||
|
|
||||||
const { pubKey, connect } = useVegaWallet();
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const [query] = useState(searchParams.get('address'));
|
|
||||||
if (query && !pubKey) {
|
|
||||||
connect(Connectors['view']);
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
update({ eagerConnecting });
|
|
||||||
}, [update, eagerConnecting]);
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -22,14 +22,17 @@ export default function Document() {
|
|||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" content="/favicon.ico" />
|
<link rel="apple-touch-icon" content="/favicon.ico" />
|
||||||
|
|
||||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
|
||||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
|
||||||
|
|
||||||
{/* scripts */}
|
{/* scripts */}
|
||||||
<script src="/theme-setter.js" type="text/javascript" async />
|
<script src="/theme-setter.js" type="text/javascript" async />
|
||||||
</Head>
|
</Head>
|
||||||
<Html>
|
<Html>
|
||||||
<body className="bg-white dark:bg-vega-cdark-900 text-default font-alpha">
|
<body
|
||||||
|
// Nextjs will set body to display none until js runs. Because the entire app is client rendered
|
||||||
|
// and delivered via ipfs we override this to show a server side render loading animation until the
|
||||||
|
// js is downloaded and react takes over rendering
|
||||||
|
style={{ display: 'block' }}
|
||||||
|
className="bg-white dark:bg-vega-cdark-900 text-default font-alpha"
|
||||||
|
>
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { RouteObject } from 'react-router-dom';
|
import type { RouteObject } from 'react-router-dom';
|
||||||
import { Navigate, Outlet, useRoutes } from 'react-router-dom';
|
import { Navigate, useRoutes } from 'react-router-dom';
|
||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
@@ -13,7 +13,7 @@ import { Assets } from '../client-pages/assets';
|
|||||||
import { Deposit } from '../client-pages/deposit';
|
import { Deposit } from '../client-pages/deposit';
|
||||||
import { Withdraw } from '../client-pages/withdraw';
|
import { Withdraw } from '../client-pages/withdraw';
|
||||||
import { Transfer } from '../client-pages/transfer';
|
import { Transfer } from '../client-pages/transfer';
|
||||||
import { Routes } from '../lib/links';
|
import { Routes as AppRoutes } from '../lib/links';
|
||||||
import { LayoutWithSky } from '../client-pages/referrals/layout';
|
import { LayoutWithSky } from '../client-pages/referrals/layout';
|
||||||
import { Referrals } from '../client-pages/referrals/referrals';
|
import { Referrals } from '../client-pages/referrals/referrals';
|
||||||
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
|
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
|
||||||
@@ -22,11 +22,15 @@ import { CreateCodeContainer } from '../client-pages/referrals/create-code-form'
|
|||||||
import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary';
|
import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary';
|
||||||
import { compact } from 'lodash';
|
import { compact } from 'lodash';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
import { LiquidityHeader } from '../components/liquidity-header';
|
||||||
|
import { MarketHeader } from '../components/market-header';
|
||||||
|
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
|
||||||
|
import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar';
|
||||||
|
import { MarketsSidebar } from '../client-pages/markets/markets-sidebar';
|
||||||
|
|
||||||
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
|
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
|
||||||
// Using dynamic imports is a workaround for this until pennant is published as ESM
|
// Using dynamic imports is a workaround for this until pennant is published as ESM
|
||||||
const MarketPage = lazy(() => import('../client-pages/market'));
|
const MarketPage = lazy(() => import('../client-pages/market'));
|
||||||
const ClosedMarketPage = lazy(() => import('../client-pages/closed-market'));
|
|
||||||
const Portfolio = lazy(() => import('../client-pages/portfolio'));
|
const Portfolio = lazy(() => import('../client-pages/portfolio'));
|
||||||
|
|
||||||
const NotFound = () => (
|
const NotFound = () => (
|
||||||
@@ -36,18 +40,21 @@ const NotFound = () => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const routerConfig: RouteObject[] = compact([
|
export const routerConfig: RouteObject[] = compact([
|
||||||
// Pages that don't use the LayoutWithSidebar must come first
|
{
|
||||||
// to ensure they are matched before the catch all route '/*'
|
index: true,
|
||||||
|
element: <Home />,
|
||||||
|
id: AppRoutes.HOME,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'disclaimer',
|
path: 'disclaimer',
|
||||||
element: <LayoutCentered />,
|
element: <LayoutCentered />,
|
||||||
id: Routes.DISCLAIMER,
|
id: AppRoutes.DISCLAIMER,
|
||||||
children: [{ index: true, element: <Disclaimer /> }],
|
children: [{ index: true, element: <Disclaimer /> }],
|
||||||
},
|
},
|
||||||
// Referrals routing (the pages should be available if the feature flag is on)
|
// Referrals routing (the pages should be available if the feature flag is on)
|
||||||
FLAGS.REFERRALS
|
FLAGS.REFERRALS
|
||||||
? {
|
? {
|
||||||
path: Routes.REFERRALS,
|
path: AppRoutes.REFERRALS,
|
||||||
element: <LayoutWithSky />,
|
element: <LayoutWithSky />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -58,11 +65,11 @@ export const routerConfig: RouteObject[] = compact([
|
|||||||
element: <ReferralStatistics />,
|
element: <ReferralStatistics />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: Routes.REFERRALS_CREATE_CODE,
|
path: AppRoutes.REFERRALS_CREATE_CODE,
|
||||||
element: <CreateCodeContainer />,
|
element: <CreateCodeContainer />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: Routes.REFERRALS_APPLY_CODE,
|
path: AppRoutes.REFERRALS_APPLY_CODE,
|
||||||
element: <ApplyCodeForm />,
|
element: <ApplyCodeForm />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -74,75 +81,74 @@ export const routerConfig: RouteObject[] = compact([
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
// All other pages will use the sidebar
|
|
||||||
{
|
{
|
||||||
path: '/*',
|
path: 'markets/*',
|
||||||
element: <LayoutWithSidebar />,
|
element: (
|
||||||
|
<LayoutWithSidebar
|
||||||
|
header={<MarketHeader />}
|
||||||
|
sidebar={<MarketsSidebar />}
|
||||||
|
/>
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
element: <Home />,
|
element: <MarketsPage />,
|
||||||
id: Routes.HOME,
|
id: AppRoutes.MARKETS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'markets',
|
path: 'all',
|
||||||
element: <Outlet />,
|
element: <Navigate to="/markets" />,
|
||||||
children: [
|
|
||||||
{
|
|
||||||
index: true,
|
|
||||||
element: <Navigate to="all" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'all',
|
|
||||||
element: <MarketsPage />,
|
|
||||||
id: Routes.MARKETS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ':marketId',
|
|
||||||
element: <MarketPage />,
|
|
||||||
id: Routes.MARKET,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'all/closed/:marketId',
|
|
||||||
element: <ClosedMarketPage />,
|
|
||||||
id: Routes.CLOSED_MARKETS,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'portfolio',
|
path: ':marketId',
|
||||||
element: <Outlet />,
|
element: <MarketPage />,
|
||||||
children: [
|
id: AppRoutes.MARKET,
|
||||||
{
|
|
||||||
index: true,
|
|
||||||
element: <Portfolio />,
|
|
||||||
id: Routes.PORTFOLIO,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'assets',
|
|
||||||
element: <Assets />,
|
|
||||||
id: Routes.ASSETS,
|
|
||||||
children: [
|
|
||||||
{ index: true, element: <Navigate to="deposit" /> },
|
|
||||||
{ path: 'deposit', element: <Deposit />, id: Routes.DEPOSIT },
|
|
||||||
{ path: 'withdraw', element: <Withdraw />, id: Routes.WITHDRAW },
|
|
||||||
{ path: 'transfer', element: <Transfer />, id: Routes.TRANSFER },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'liquidity/:marketId',
|
|
||||||
element: <Liquidity />,
|
|
||||||
id: Routes.LIQUIDITY,
|
|
||||||
},
|
|
||||||
// NotFound page is here so its caught within parent '/*' route
|
|
||||||
{
|
|
||||||
path: '*',
|
|
||||||
element: <NotFound />,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'portfolio/*',
|
||||||
|
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: <Portfolio />,
|
||||||
|
id: AppRoutes.PORTFOLIO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'assets',
|
||||||
|
element: <Assets />,
|
||||||
|
id: AppRoutes.ASSETS,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Navigate to="deposit" /> },
|
||||||
|
{ path: 'deposit', element: <Deposit />, id: AppRoutes.DEPOSIT },
|
||||||
|
{ path: 'withdraw', element: <Withdraw />, id: AppRoutes.WITHDRAW },
|
||||||
|
{ path: 'transfer', element: <Transfer />, id: AppRoutes.TRANSFER },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'liquidity/*',
|
||||||
|
element: (
|
||||||
|
<LayoutWithSidebar
|
||||||
|
header={<LiquidityHeader />}
|
||||||
|
sidebar={<LiquiditySidebar />}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
id: AppRoutes.LIQUIDITY,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ':marketId',
|
||||||
|
element: <Liquidity />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
element: <NotFound />,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const ClientRouter = () => {
|
export const ClientRouter = () => {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
import { useEagerConnect as useEthereumEagerConnect } from '@vegaprotocol/web3';
|
||||||
|
import {
|
||||||
|
useEagerConnect as useVegaEagerConnect,
|
||||||
|
useVegaWallet,
|
||||||
|
} from '@vegaprotocol/wallet';
|
||||||
|
import { useGlobalStore } from '../stores';
|
||||||
|
import { Connectors } from '../lib/vega-connectors';
|
||||||
|
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||||
|
|
||||||
|
export const MaybeConnectEagerly = () => {
|
||||||
|
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||||
|
const update = useGlobalStore((store) => store.update);
|
||||||
|
const eagerConnecting = useVegaEagerConnect(Connectors);
|
||||||
|
const [isTelemetryApproved] = useTelemetryApproval();
|
||||||
|
useEthereumEagerConnect(
|
||||||
|
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { pubKey, connect } = useVegaWallet();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [query] = useState(searchParams.get('address'));
|
||||||
|
if (query && !pubKey) {
|
||||||
|
connect(Connectors['view']);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
update({ eagerConnecting });
|
||||||
|
}, [update, eagerConnecting]);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
|
export const PartyActiveOrdersHandler = () => {
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
const variables = { partyId: pubKey || '' };
|
||||||
|
const skip = !pubKey;
|
||||||
|
useDataProvider({
|
||||||
|
dataProvider: activeOrdersProvider,
|
||||||
|
variables,
|
||||||
|
skip,
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { pseudoRandom } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
const generate = pseudoRandom(1);
|
||||||
|
|
||||||
|
export const SSRLoader = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<style jsx>{`
|
||||||
|
@keyframes flickering {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
26% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<div style={{ width: 50, height: 50, display: 'flex', flexWrap: 'wrap' }}>
|
||||||
|
{new Array(25).fill(null).map((_, i) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
animation: 'flickering 0.4s linear alternate infinite',
|
||||||
|
animationDelay: `-${generate()}s`,
|
||||||
|
animationDirection: i % 2 === 0 ? 'reverse' : 'alternate',
|
||||||
|
background: 'black',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
useVegaTransactionManager,
|
||||||
|
useVegaTransactionUpdater,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
|
import {
|
||||||
|
useEthTransactionManager,
|
||||||
|
useEthTransactionUpdater,
|
||||||
|
useEthWithdrawApprovalsManager,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
|
import { useLedgerDownloadManager } from '@vegaprotocol/ledger';
|
||||||
|
|
||||||
|
export const TransactionHandlers = () => {
|
||||||
|
useVegaTransactionManager();
|
||||||
|
useVegaTransactionUpdater();
|
||||||
|
useEthTransactionManager();
|
||||||
|
useEthTransactionUpdater();
|
||||||
|
useEthWithdrawApprovalsManager();
|
||||||
|
useLedgerDownloadManager();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
.pre-loader {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
.pre-loader .pre-loader-center {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pre-loader .pre-loader-wrapper {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
.pre-loader .loader-item {
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(0) {
|
|
||||||
animation-delay: 0ms;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:first-child {
|
|
||||||
animation-delay: -50ms;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(3) {
|
|
||||||
animation-delay: -0.6s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(4) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(5) {
|
|
||||||
animation-delay: -0.5s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(6) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(7) {
|
|
||||||
animation-delay: -1.4s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(8) {
|
|
||||||
animation-delay: 2s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(9) {
|
|
||||||
animation-delay: -0.9s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(10) {
|
|
||||||
animation-delay: 1.5s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(11) {
|
|
||||||
animation-delay: -0.55s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(12) {
|
|
||||||
animation-delay: 1.2s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(13) {
|
|
||||||
animation-delay: -0.65s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(14) {
|
|
||||||
animation-delay: 0.7s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(15) {
|
|
||||||
animation-delay: -3.75s;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item:nth-child(16) {
|
|
||||||
animation-delay: 1.6s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
.pre-loader .loader-item {
|
|
||||||
animation: flickering 0.4s linear infinite alternate;
|
|
||||||
}
|
|
||||||
@keyframes flickering {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
26% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html.dark .pre-loader .loader-item {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
@@ -45,6 +45,28 @@ const secondRow = {
|
|||||||
} as AccountFields;
|
} as AccountFields;
|
||||||
const multiRowData = [singleRow, secondRow];
|
const multiRowData = [singleRow, secondRow];
|
||||||
|
|
||||||
|
const zeroBalanceRow = {
|
||||||
|
__typename: 'AccountBalance',
|
||||||
|
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||||
|
balance: '0',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||||
|
symbol: 'tBTC',
|
||||||
|
decimals: 5,
|
||||||
|
},
|
||||||
|
available: '0',
|
||||||
|
used: '0',
|
||||||
|
total: '0',
|
||||||
|
} as AccountFields;
|
||||||
|
const zeroBalanceRowData = [zeroBalanceRow];
|
||||||
|
|
||||||
|
const onClickDepositMock = jest.fn();
|
||||||
|
|
||||||
describe('AccountsTable', () => {
|
describe('AccountsTable', () => {
|
||||||
it('should render correct columns', async () => {
|
it('should render correct columns', async () => {
|
||||||
// 7001-COLL-001
|
// 7001-COLL-001
|
||||||
@@ -71,6 +93,27 @@ describe('AccountsTable', () => {
|
|||||||
).toEqual(expectedHeaders);
|
).toEqual(expectedHeaders);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render deposit button', async () => {
|
||||||
|
render(
|
||||||
|
<AccountTable
|
||||||
|
rowData={zeroBalanceRowData}
|
||||||
|
onClickAsset={() => null}
|
||||||
|
isReadOnly={false}
|
||||||
|
onClickDeposit={onClickDepositMock}
|
||||||
|
pinnedAsset={{
|
||||||
|
decimals: 5,
|
||||||
|
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||||
|
symbol: 'tBTC',
|
||||||
|
name: 'tBTC',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const depositButton = screen.getByTestId('deposit');
|
||||||
|
expect(depositButton).toBeVisible();
|
||||||
|
await userEvent.click(depositButton);
|
||||||
|
expect(onClickDepositMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should apply correct formatting', async () => {
|
it('should apply correct formatting', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<AccountTable
|
<AccountTable
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ describe('TransferForm', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('validates a manually entered address', async () => {
|
it('validates a manually entered address', async () => {
|
||||||
|
// 1003-TRAN-012
|
||||||
|
// 1003-TRAN-013
|
||||||
|
// 1003-TRAN-004
|
||||||
render(<TransferForm {...props} />);
|
render(<TransferForm {...props} />);
|
||||||
submit();
|
submit();
|
||||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||||
@@ -116,6 +119,11 @@ describe('TransferForm', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('validates fields and submits', async () => {
|
it('validates fields and submits', async () => {
|
||||||
|
// 1003-TRAN-002
|
||||||
|
// 1003-TRAN-003
|
||||||
|
// 1002-WITH-010
|
||||||
|
// 1003-TRAN-011
|
||||||
|
// 1003-TRAN-014
|
||||||
render(<TransferForm {...props} />);
|
render(<TransferForm {...props} />);
|
||||||
|
|
||||||
// check current pubkey not shown
|
// check current pubkey not shown
|
||||||
|
|||||||
@@ -57,7 +57,16 @@ export const rows: Rows = [
|
|||||||
key: AssetDetail.ID,
|
key: AssetDetail.ID,
|
||||||
label: t('ID'),
|
label: t('ID'),
|
||||||
tooltip: '',
|
tooltip: '',
|
||||||
value: (asset) => truncateMiddle(asset.id),
|
value: (asset) => (
|
||||||
|
<>
|
||||||
|
{truncateMiddle(asset.id)}{' '}
|
||||||
|
<CopyWithTooltip text={asset.id}>
|
||||||
|
<button title={t('Copy id to clipboard')}>
|
||||||
|
<VegaIcon size={14} name={VegaIconNames.COPY} />
|
||||||
|
</button>
|
||||||
|
</CopyWithTooltip>
|
||||||
|
</>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: AssetDetail.TYPE,
|
key: AssetDetail.TYPE,
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import {
|
|||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { AssetsDocument } from './__generated__/Assets';
|
import { AssetsDocument } from './__generated__/Assets';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import { AssetStatus } from '@vegaprotocol/types';
|
||||||
import type { AssetsQuery } from './__generated__/Assets';
|
import type { AssetsQuery } from './__generated__/Assets';
|
||||||
import type { Asset } from './asset-data-provider';
|
import type { Asset } from './asset-data-provider';
|
||||||
|
import { DENY_LIST } from './constants';
|
||||||
|
|
||||||
export interface BuiltinAssetSource {
|
export interface BuiltinAssetSource {
|
||||||
__typename: 'BuiltinAsset';
|
__typename: 'BuiltinAsset';
|
||||||
@@ -48,6 +49,7 @@ export const assetsMapProvider = makeDerivedDataProvider<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Returns a record of assets by id */
|
||||||
export const useAssetsMapProvider = () =>
|
export const useAssetsMapProvider = () =>
|
||||||
useDataProvider({
|
useDataProvider({
|
||||||
dataProvider: assetsMapProvider,
|
dataProvider: assetsMapProvider,
|
||||||
@@ -58,13 +60,32 @@ export const enabledAssetsProvider = makeDerivedDataProvider<
|
|||||||
ReturnType<typeof getData>,
|
ReturnType<typeof getData>,
|
||||||
never
|
never
|
||||||
>([assetsProvider], ([assets]) =>
|
>([assetsProvider], ([assets]) =>
|
||||||
(assets as ReturnType<typeof getData>).filter(
|
(assets as ReturnType<typeof getData>)
|
||||||
(a) => a.status === Schema.AssetStatus.STATUS_ENABLED
|
.filter((a) => a.status === AssetStatus.STATUS_ENABLED)
|
||||||
)
|
.filter((a) => {
|
||||||
|
const env = process.env['NX_VEGA_ENV'];
|
||||||
|
|
||||||
|
if (!env || !DENY_LIST[env]) return true;
|
||||||
|
|
||||||
|
if (DENY_LIST[env].includes(a.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Returns all assets */
|
||||||
export const useAssetsDataProvider = () =>
|
export const useAssetsDataProvider = () =>
|
||||||
useDataProvider({
|
useDataProvider({
|
||||||
dataProvider: assetsProvider,
|
dataProvider: assetsProvider,
|
||||||
variables: undefined,
|
variables: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Returns assets that are enabled and are not on the deny list */
|
||||||
|
export const useEnabledAssets = () => {
|
||||||
|
return useDataProvider({
|
||||||
|
dataProvider: enabledAssetsProvider,
|
||||||
|
variables: undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,67 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
|
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
|
||||||
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
|
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// List of defunct and no longer used assets that were created for various testnets
|
||||||
|
export const DENY_LIST: Record<string, string[]> = {
|
||||||
|
TESTNET: [
|
||||||
|
'07f8ce4e3df6c649f3fd794112f545ede9fbd9cfa76a9e380a7502a8d525e4db',
|
||||||
|
'0b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b379',
|
||||||
|
'169fd651dd1303fc6f3e1dee8349439140cf467a190bc9a04bff3e617509cfd4',
|
||||||
|
'16ae5dbb1fd7aa2ddef725703bfe66b3647a4da7b844bfdd04e985756f53d9d6',
|
||||||
|
'177e8f6c25a955bd18475084b99b2b1d37f28f3dec393fab7755a7e69c3d8c3b',
|
||||||
|
'20ffe5b1f70364a6b976007d96d306345bc7b880256ddffbbea2c590096ef375',
|
||||||
|
'2792b6ead78a812bd0169b2adbe9c2ea5b8c21510e40b93bf8d2cba6a0838e90',
|
||||||
|
'27c0229ab9edc6a3ba6a3e9716c1b324d6515a3168004256879e2de7c240f453',
|
||||||
|
'285923fed8c66ffb416b163e8ec72d3a87b9b8e2570e7ee7fe97d7092a918bc8',
|
||||||
|
'2af02165ef5ea1d1284b4251117eaae45faa961ec918e3f69b66903a18822387',
|
||||||
|
'2bf406160d06280d92412605eb397d08142736f4e17967e404dc892da278e9da',
|
||||||
|
'373d90d97bd3f97f0c13cf449926cfd7f1ed5f3746c3091e8dd894d60bf5eac8',
|
||||||
|
'3c7d6bd83dac3174923ba65f3af01f42858e267ca5b722fe2b0b3fec14e98ec2',
|
||||||
|
'434b502cabb7a9a36d3036a599d589537da293d8bbab224692629351a7926249',
|
||||||
|
'43bb50d6ba0a871088014cb204079ace4811970ce3d52b15896f6e7a88f900b7',
|
||||||
|
'46e9d23142762db57751b1b507e15747fe9478de504cbc4a0bffd828c39e3776',
|
||||||
|
'4b6957c6a184b002db80c7a4dd811d0e70866be4a4c8fffa4894e78aa132b407',
|
||||||
|
'4cbe1ad4cf232fb2232176e3f440f6e6fdc3ba7b1d95a99cb69116b9e379eae8',
|
||||||
|
'4e4e80abff30cab933b8c4ac6befc618372eb76b2cbddc337eff0b4a3a4d25b8',
|
||||||
|
'51eed0c17e3287996ba6006ce56de2591550ebfad46631f3a0129886967420e1',
|
||||||
|
'522e09779d984ae77b60036f4ecd80f0491f5f0c8cc386a0712b78a020ab62e6',
|
||||||
|
'5c950d4cfe1aaf9174c4358ee5df03f7e6bc556eac06a012cf6c07db154e20c4',
|
||||||
|
'5d8eb4e4e385b85c5c11f47111f315d5d201e04f4ef5e294763bf51036974b91',
|
||||||
|
'5dd5c226e944b4c3df3e8dbbbc473e72fb134f4c5b82a1968cc3cf7c1b40fe9e',
|
||||||
|
'638b4dbe4f3ce5b81546bef29108e8374a7293012c6189ca00f287f8c62a98a5',
|
||||||
|
'644c3a6e258382542a108c4dca16fae93657b6ff40238b2bb55854a125316052',
|
||||||
|
'71ea74ef1aa7582bcdd4ef843b3e79584ded4d16e692f3946cac4c9d61aed4f2',
|
||||||
|
'72f051ea66686ae004086f1ad086866f720f25896319abf3427cae101a58d985',
|
||||||
|
'827bbacde0dd5bfec857933dbbf642147f2afe6a9db0a543839b1b5239ca6786',
|
||||||
|
'83f4970aa1aeab85d01d8a976cbfd0fb3afedf391e18197e8c94c4021b3dffa1',
|
||||||
|
'84a20b554854c9af10ef84d308795dfae455834d1a207798292d66e19fd72c92',
|
||||||
|
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f',
|
||||||
|
'85771250f0aa84727a1c01ba8926e59217ca13a7706ba03cf2ad95abbbc2e3bc',
|
||||||
|
'8a246b15bf185065d3f41a710f3eea0fbbe8754a8464501cf4a6d253d69c6423',
|
||||||
|
'8e5553ee4e95db51523640d1d03b00c8ab31dd69d787e169b0d958ef52e19a0a',
|
||||||
|
'92747340d922e9cda5bde17c46af8b409e47d6342f3b9c8fb058a34c6206d373',
|
||||||
|
'9b43c6791aebed5f7b38d91907539173aa278de88294b6422f3dd8282cd2c6e2',
|
||||||
|
'9e0bb9bd7ea2ec51efcdc98b432b6f0b055b2ed7973cfac9b44899d6e6c5deab',
|
||||||
|
'a0cf0835cb5af7bd03c56d12d846eed81c182eab36ea61c074961c115da9c8c4',
|
||||||
|
'a26dc4f6dea91e80d42e4736da7fe3c42f6275df9c4dca48be3ef96850192242',
|
||||||
|
'a4c945aea50ef321eabf8a3bcf0b8bb74288452c4638876ee4df5fb59be4a0ec',
|
||||||
|
'ae28c1043e79bdb95cae0e9919d4436157d14b183e24ab4572793812f3d89222',
|
||||||
|
'b0cf8be4e4d1d2ac5156c2298aa42edc7a74d25b7838d06e2279e94018a47f3f',
|
||||||
|
'b6d11705cb456768ff8271074dd8adcd78bd042a27fc205e9b52f9f945f42ea1',
|
||||||
|
'be478570584211521890fb65a41bded4d2bf29beb0b61ac3cc3b97f828a27b22',
|
||||||
|
'befde6d78ea1c996ae1d959342730220e274992097aeeae00c908a971314e6ae',
|
||||||
|
'bf5b9464d154ffeff57f05f90b223c1045f0754befd01a9a42c99382115e7833',
|
||||||
|
'd2239a7c92bc5c72a959a73ccee7727ba0966f4d88a33d71dbbc26e3f171555d',
|
||||||
|
'd3f9cf6e1711595b4588366a96b725fc039f72a8a28b2e4c1b630016dd2eed39',
|
||||||
|
'd9a9ab00bb79ce7dca7f6329ab627dcea2e53e5c16b85bf4aa8c3980b76fad57',
|
||||||
|
'dd73f7d1e721ef5cba95f454a3019521456c1b43b97084ad29e3e6509148d45d',
|
||||||
|
'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
|
||||||
|
'ede4076aef07fd79502d14326c54ab3911558371baaf697a19d077f4f89de399',
|
||||||
|
'f481abe5700c6940ca996adfa9d07c9d9f07a486a7ce9cdb2e6ee0fa6450fe31',
|
||||||
|
'f7355ec52730c2e6ba02216da8501b4b6934cd363511e9d47ae5b97056d02703',
|
||||||
|
'fdf0ec118d98393a7702cf72e46fc87ad680b152f64b2aac59e093ac2d688fbb',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ export const CandlesChartContainer = ({
|
|||||||
overlays,
|
overlays,
|
||||||
studies,
|
studies,
|
||||||
notEnoughDataText: (
|
notEnoughDataText: (
|
||||||
<span className="text-xs text-center">{t('No data')}</span>
|
<span className="text-xs text-center">
|
||||||
|
{t('No open orders')}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
initialNumCandlesToDisplay: candlesCount,
|
initialNumCandlesToDisplay: candlesCount,
|
||||||
studySize: STUDY_SIZE,
|
studySize: STUDY_SIZE,
|
||||||
|
|||||||
@@ -14,16 +14,17 @@ export const MarketProductPill = ({
|
|||||||
}: {
|
}: {
|
||||||
productType?: ProductType;
|
productType?: ProductType;
|
||||||
}) => {
|
}) => {
|
||||||
|
if (!productType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
productType && (
|
<Pill
|
||||||
<Pill
|
size="xxs"
|
||||||
size="xxs"
|
className="uppercase ml-0.5"
|
||||||
className="uppercase ml-0.5"
|
title={ProductTypeMapping[productType]}
|
||||||
title={ProductTypeMapping[productType]}
|
>
|
||||||
>
|
{ProductTypeShortName[productType]}
|
||||||
{ProductTypeShortName[productType]}
|
</Pill>
|
||||||
</Pill>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
AccordionChevron,
|
AccordionChevron,
|
||||||
AccordionPanel,
|
AccordionPanel,
|
||||||
Intent,
|
Intent,
|
||||||
|
ExternalLink,
|
||||||
Pill,
|
Pill,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
@@ -385,7 +386,21 @@ export const DealTicketMarginDetails = ({
|
|||||||
value={liquidationPriceEstimateRange}
|
value={liquidationPriceEstimateRange}
|
||||||
formattedValue={liquidationPriceEstimate}
|
formattedValue={liquidationPriceEstimate}
|
||||||
symbol={quoteName}
|
symbol={quoteName}
|
||||||
labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}
|
labelDescription={
|
||||||
|
<>
|
||||||
|
<span>{LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}</span>{' '}
|
||||||
|
<span>
|
||||||
|
{t('For full details please see ')}
|
||||||
|
<ExternalLink
|
||||||
|
href={
|
||||||
|
'https://github.com/vegaprotocol/specs/blob/master/non-protocol-specs/0012-NP-LIPE-liquidation-price-estimate.md'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('liquidation price estimate documentation.')}
|
||||||
|
</ExternalLink>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{partyId && (
|
{partyId && (
|
||||||
<AccountBreakdownDialog
|
<AccountBreakdownDialog
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import {
|
|||||||
REDUCE_ONLY_TOOLTIP,
|
REDUCE_ONLY_TOOLTIP,
|
||||||
stopSubmit,
|
stopSubmit,
|
||||||
getNotionalSize,
|
getNotionalSize,
|
||||||
getAssetUnit,
|
getBaseQuoteUnit,
|
||||||
} from './deal-ticket';
|
} from './deal-ticket';
|
||||||
import { TypeToggle } from './type-selector';
|
import { TypeToggle } from './type-selector';
|
||||||
import {
|
import {
|
||||||
@@ -854,7 +854,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
|||||||
}, [watch, market.id, updateStoredFormValues]);
|
}, [watch, market.id, updateStoredFormValues]);
|
||||||
|
|
||||||
const quoteName = getQuoteName(market);
|
const quoteName = getQuoteName(market);
|
||||||
const assetUnit = getAssetUnit(
|
const assetUnit = getBaseQuoteUnit(
|
||||||
market.tradableInstrument.instrument.metadata.tags
|
market.tradableInstrument.instrument.metadata.tags
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ const getDefaultValues = (
|
|||||||
...storedValues,
|
...storedValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAssetUnit = (tags?: string[] | null) =>
|
export const getBaseQuoteUnit = (tags?: string[] | null) =>
|
||||||
tags
|
tags
|
||||||
?.find((tag) => tag.startsWith('base:') || tag.startsWith('ticker:'))
|
?.find((tag) => tag.startsWith('base:') || tag.startsWith('ticker:'))
|
||||||
?.replace(/^[^:]*:/, '');
|
?.replace(/^[^:]*:/, '');
|
||||||
@@ -273,9 +273,7 @@ export const DealTicket = ({
|
|||||||
|
|
||||||
const assetSymbol = getAsset(market).symbol;
|
const assetSymbol = getAsset(market).symbol;
|
||||||
|
|
||||||
const assetUnit = getAssetUnit(
|
const assetUnit = getQuoteName(market);
|
||||||
market.tradableInstrument.instrument.metadata.tags
|
|
||||||
);
|
|
||||||
|
|
||||||
const summaryError = useMemo(() => {
|
const summaryError = useMemo(() => {
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const EST_FEES_TOOLTIP_TEXT = t(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
|
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
|
||||||
'This is an approximation (or a range) for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
'This is an approximation for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const EST_SLIPPAGE = t(
|
export const EST_SLIPPAGE = t(
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
|
||||||
import { DepositManager } from './deposit-manager';
|
import { DepositManager } from './deposit-manager';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useEnabledAssets } from '@vegaprotocol/assets';
|
||||||
import { enabledAssetsProvider } from '@vegaprotocol/assets';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches data required for the Deposit page
|
* Fetches data required for the Deposit page
|
||||||
*/
|
*/
|
||||||
export const DepositContainer = ({ assetId }: { assetId?: string }) => {
|
export const DepositContainer = ({ assetId }: { assetId?: string }) => {
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
const { data, loading, error } = useDataProvider({
|
const { data, error, loading } = useEnabledAssets();
|
||||||
dataProvider: enabledAssetsProvider,
|
|
||||||
variables: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRendererInline data={data} loading={loading} error={error}>
|
<AsyncRendererInline data={data} loading={loading} error={error}>
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ describe('Deposit form', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||||
|
// 1001-DEPO-004
|
||||||
expect(
|
expect(
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
"You can't deposit more than you have in your Ethereum wallet, 5"
|
"You can't deposit more than you have in your Ethereum wallet, 5"
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useNodeSwitcherStore } from '../../hooks/use-node-switcher-store';
|
|
||||||
|
|
||||||
export const AppFailure = ({
|
export const AppFailure = ({
|
||||||
title,
|
title,
|
||||||
@@ -9,18 +7,10 @@ export const AppFailure = ({
|
|||||||
title: string;
|
title: string;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
|
|
||||||
const nonIdealWrapperClasses =
|
|
||||||
'h-full min-h-screen flex items-center justify-center';
|
|
||||||
return (
|
return (
|
||||||
<div className={nonIdealWrapperClasses}>
|
<Splash>
|
||||||
<div className="text-center">
|
<p className="mb-4 text-xl">{title}</p>
|
||||||
<p className="text-xl mb-4">{title}</p>
|
{error && <p className="mb-8 text-sm">{error}</p>}
|
||||||
{error && <p className="text-sm mb-8">{error}</p>}
|
</Splash>
|
||||||
<Button onClick={() => setNodeSwitcher(true)}>
|
|
||||||
{t('Change node')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
export const AppLoader = () => {
|
||||||
|
return (
|
||||||
|
<Splash>
|
||||||
|
<Loader />
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AppLoader } from './app-loader';
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
export * from './app-failure';
|
export * from './app-failure';
|
||||||
|
export * from './app-loader';
|
||||||
export * from './network-loader';
|
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 './node-failure';
|
||||||
export * from './etherscan-link';
|
export * from './etherscan-link';
|
||||||
|
|||||||
@@ -33,15 +33,14 @@ export function NetworkLoader({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [VEGA_URL, status, cache]);
|
}, [VEGA_URL, status, cache]);
|
||||||
|
|
||||||
const nonIdealWrapperClasses =
|
|
||||||
'h-full min-h-screen flex items-center justify-center';
|
|
||||||
|
|
||||||
if (status === 'failed') {
|
if (status === 'failed') {
|
||||||
return <div className={nonIdealWrapperClasses}>{failure}</div>;
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{failure}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'default' || status === 'pending' || !client) {
|
if (status === 'default' || status === 'pending' || !client) {
|
||||||
return <div className={nonIdealWrapperClasses}>{skeleton}</div>;
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{skeleton}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { NodeFailure } from './node-failure';
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useNodeSwitcherStore } from '../../hooks/use-node-switcher-store';
|
||||||
|
|
||||||
|
export const NodeFailure = ({
|
||||||
|
title,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
error?: string | null;
|
||||||
|
}) => {
|
||||||
|
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
|
||||||
|
return (
|
||||||
|
<Splash>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="mb-4 text-xl">{title}</p>
|
||||||
|
{error && <p className="mb-8 text-sm">{error}</p>}
|
||||||
|
<Button onClick={() => setNodeSwitcher(true)}>
|
||||||
|
{t('Change node')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,11 +11,10 @@ export const NodeGuard = ({
|
|||||||
skeleton: ReactNode;
|
skeleton: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const { data, error, loading } = useNodeGuardQuery();
|
const { data, error, loading } = useNodeGuardQuery();
|
||||||
const wrapperClasses =
|
|
||||||
'h-full min-h-screen flex items-center justify-center text-black dark:text-white';
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className={wrapperClasses}>{skeleton}</div>;
|
// eslint-disable-next-line
|
||||||
|
return <>{skeleton}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is possible for nodes to have a functioning datanode, but not return
|
// It is possible for nodes to have a functioning datanode, but not return
|
||||||
@@ -23,9 +22,10 @@ export const NodeGuard = ({
|
|||||||
const netParamEdges = data?.networkParametersConnection.edges;
|
const netParamEdges = data?.networkParametersConnection.edges;
|
||||||
|
|
||||||
if (error || !netParamEdges || !netParamEdges.length) {
|
if (error || !netParamEdges || !netParamEdges.length) {
|
||||||
return <div className={wrapperClasses}>{failure}</div>;
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{failure}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -338,9 +338,6 @@ function compileEnvVars() {
|
|||||||
'GITHUB_FEEDBACK_URL',
|
'GITHUB_FEEDBACK_URL',
|
||||||
process.env['NX_GITHUB_FEEDBACK_URL']
|
process.env['NX_GITHUB_FEEDBACK_URL']
|
||||||
),
|
),
|
||||||
MAINTENANCE_PAGE: parseBoolean(
|
|
||||||
windowOrDefault('MAINTENANCE_PAGE', process.env['NX_MAINTENANCE_PAGE'])
|
|
||||||
),
|
|
||||||
GIT_BRANCH: windowOrDefault(
|
GIT_BRANCH: windowOrDefault(
|
||||||
'GIT_COMMIT_BRANCH',
|
'GIT_COMMIT_BRANCH',
|
||||||
process.env['GIT_COMMIT_BRANCH']
|
process.env['GIT_COMMIT_BRANCH']
|
||||||
@@ -524,10 +521,6 @@ function parseNetworks(value?: string) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBoolean(value?: string) {
|
|
||||||
return ['true', '1', 'yes'].includes(value?.toLowerCase() || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a fallback ethereum provider url for test purposes in some apps
|
* Provides a fallback ethereum provider url for test purposes in some apps
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export const envSchema = z
|
|||||||
message: 'The NX_ETHERSCAN_URL environment variable must be a valid url',
|
message: 'The NX_ETHERSCAN_URL environment variable must be a valid url',
|
||||||
}),
|
}),
|
||||||
HOSTED_WALLET_URL: z.optional(z.string()),
|
HOSTED_WALLET_URL: z.optional(z.string()),
|
||||||
MAINTENANCE_PAGE: z.optional(z.boolean()),
|
|
||||||
ETH_LOCAL_PROVIDER_URL: z.optional(z.string()),
|
ETH_LOCAL_PROVIDER_URL: z.optional(z.string()),
|
||||||
ETH_WALLET_MNEMONIC: z.optional(z.string()),
|
ETH_WALLET_MNEMONIC: z.optional(z.string()),
|
||||||
ANNOUNCEMENTS_CONFIG_URL: z.optional(z.string()),
|
ANNOUNCEMENTS_CONFIG_URL: z.optional(z.string()),
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ fragment FundingPaymentFields on FundingPayment {
|
|||||||
timestamp
|
timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
query FundingPayments($partyId: ID!, $pagination: Pagination) {
|
query FundingPayments($partyId: ID!, $pagination: Pagination, $marketId: ID) {
|
||||||
fundingPayments(partyId: $partyId, pagination: $pagination) {
|
fundingPayments(
|
||||||
|
partyId: $partyId
|
||||||
|
pagination: $pagination
|
||||||
|
marketId: $marketId
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...FundingPaymentFields
|
...FundingPaymentFields
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type FundingPaymentFieldsFragment = { __typename?: 'FundingPayment', mark
|
|||||||
export type FundingPaymentsQueryVariables = Types.Exact<{
|
export type FundingPaymentsQueryVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
pagination?: Types.InputMaybe<Types.Pagination>;
|
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
|
marketId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
@@ -23,8 +24,8 @@ export const FundingPaymentFieldsFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const FundingPaymentsDocument = gql`
|
export const FundingPaymentsDocument = gql`
|
||||||
query FundingPayments($partyId: ID!, $pagination: Pagination) {
|
query FundingPayments($partyId: ID!, $pagination: Pagination, $marketId: ID) {
|
||||||
fundingPayments(partyId: $partyId, pagination: $pagination) {
|
fundingPayments(partyId: $partyId, pagination: $pagination, marketId: $marketId) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...FundingPaymentFields
|
...FundingPaymentFields
|
||||||
@@ -55,6 +56,7 @@ export const FundingPaymentsDocument = gql`
|
|||||||
* variables: {
|
* variables: {
|
||||||
* partyId: // value for 'partyId'
|
* partyId: // value for 'partyId'
|
||||||
* pagination: // value for 'pagination'
|
* pagination: // value for 'pagination'
|
||||||
|
* marketId: // value for 'marketId'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user