Compare commits

..
Author SHA1 Message Date
Madalina Raicu 8d2452ac5c test: update market-liquidity.cy.ts 2023-10-20 09:25:10 +01:00
Madalina Raicu ca607d4d3d test: update market-liquidity.cy.ts 2023-10-19 13:20:24 +01:00
Madalina Raicu 13ae166235 chore: update headers in test 2023-10-19 11:31:15 +01:00
Madalina Raicu bee7b9faad Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/lp-sla 2023-10-19 09:22:30 +01:00
Madalina Raicu 6fe8fbc681 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/lp-sla 2023-10-18 11:21:15 +01:00
Madalina Raicu a22e2f0921 feat: remove grouping 2023-10-18 11:18:35 +01:00
Madalina Raicu 02fc056def chore: update liq. table 2023-10-18 10:30:48 +01:00
Madalina Raicu 209a430707 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/lp-sla 2023-10-17 13:51:13 +01:00
Madalina Raicu 6cc32eb03e Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/lp-sla 2023-10-17 10:51:25 +01:00
Madalina Raicu 7897f08a88 fix: make header group labels translatable 2023-10-17 10:26:58 +01:00
Madalina Raicu 2ea15c4dab Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/lp-sla 2023-10-16 22:38:06 +01:00
Madalina Raicu 87cffd646b chore: update remove unseud import 2023-10-16 11:29:27 +01:00
Madalina Raicu 77ae530564 chore: liquidity tooltip update 2023-10-16 10:27:02 +01:00
Madalina Raicu 45fd9563c6 Merge branch 'fix/update-referral-program-proposal-changes' of github.com:vegaprotocol/frontend-monorepo into feat/lp-sla 2023-10-16 10:26:21 +01:00
Madalina Raicu ae8a721d94 fix: update proposal referral program details test 2023-10-15 20:03:46 +01:00
Madalina Raicu 17c49454bd feat: update LP columns and groups 2023-10-15 19:34:55 +01:00
asiaznik cab087449b fix: lint issues 2023-10-13 14:03:35 +02:00
asiaznik 01842a78b0 fix: locale time format mismatch 2023-10-13 14:03:35 +02:00
asiaznik c27f08c672 fix(governance): update referral program changes 2023-10-13 14:03:35 +02:00
Madalina Raicu 8263712111 chore: update avg score formatting 2023-10-12 14:26:31 +02:00
Madalina Raicu c5105d49cb chore: fix liquidity-table tests 2023-10-12 14:13:26 +02:00
Madalina Raicu 6d864c0c3c chore: update liquidity-provider data spec 2023-10-12 13:58:25 +02:00
Madalina Raicu 8f95e8a376 feat(trading): update liquidity data providers 2023-10-12 13:50:43 +02:00
Madalina Raicu 47edb908d0 Merge branch 'fix/update-referral-program-proposal-changes' of github.com:vegaprotocol/frontend-monorepo into develop 2023-10-12 12:00:26 +02:00
Madalina Raicu 45ab8e51b5 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into develop 2023-10-12 11:59:47 +02:00
asiaznik 3f14580bbd fix: locale time format mismatch 2023-10-12 11:56:36 +02:00
asiaznik daa737c0e4 fix(governance): update referral program changes 2023-10-12 11:38:15 +02:00
88 changed files with 1790 additions and 2848 deletions
@@ -12,7 +12,6 @@ import {
SPECIAL_CASE_NETWORK,
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -64,16 +63,10 @@ export const TxDetailsTransfer = ({
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered" data-testid="type">
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
</TableRow>
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -81,7 +74,7 @@ export const TxDetailsTransfer = ({
hideTypeRow={true}
/>
{from ? (
<TableRow modifier="bordered" data-testid="from">
<TableRow modifier="bordered">
<TableCell>{t('From')}</TableCell>
<TableCell>
<PartyLink id={from} />
@@ -89,7 +82,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
{transfer.to ? (
<TableRow modifier="bordered" data-testid="to">
<TableRow modifier="bordered">
<TableCell>{t('To')}</TableCell>
<TableCell>
<PartyLink id={transfer.to} />
@@ -97,7 +90,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
{transfer.asset && transfer.amount ? (
<TableRow modifier="bordered" data-testid="amount">
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
@@ -1,15 +1,6 @@
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
import { getTypeLabelForTransfer } from './details/tx-transfer';
import type { components } from '../../../types/explorer';
import {
TxDetailsTransfer,
getTypeLabelForTransfer,
} from './details/tx-transfer';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
type Transfer = components['schemas']['commandsv1Transfer'];
describe('TX: Transfer: getLabelForTransfer', () => {
@@ -65,70 +56,3 @@ describe('TX: Transfer: getLabelForTransfer', () => {
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
});
});
describe('TxDetailsTransfer', () => {
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
const mockTxData: Partial<BlockExplorerTransactionResult> = {
hash: 'test',
submitter:
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
command: {
nonce: '5188810881378065222',
blockHeight: '14951513',
transfer: {
fromAccountType: 'ACCOUNT_TYPE_GENERAL',
to: '78432a2808f20b18a46ccc6a917bdc4d63c2b9e7007f777bdcab5a9f462c5ba6',
toAccountType: 'ACCOUNT_TYPE_GENERAL',
asset:
'dd20590509d30d20bdbbe64dc1090c1140c7690121a9b9940bc66f62dfa2e599',
amount: '4800000000',
reference: '',
oneOff: {
deliverOn: '0',
},
},
},
signature: {
value:
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
},
};
it('renders basic transfer details', () => {
const { getByTestId } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsTransfer
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockTxData.command.submitter}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
const id = getByTestId('id');
expect(id.children[0].textContent).toEqual('Transfer ID');
expect(id.children[1].textContent).toEqual(
'51f3bab5eb2637651012507a64d497790a734248792c16e5cf36df8984074fbd'
);
const type = getByTestId('type');
expect(type.children[1].textContent).toEqual('Transfer');
const from = getByTestId('from');
expect(from.children[1].textContent).toEqual(mockTxData.submitter);
const to = getByTestId('to');
expect(to.children[1].textContent).toEqual(mockTxData.command.transfer.to);
});
});
+1 -1
View File
@@ -133,7 +133,7 @@ export const ErrorBoundary = () => {
);
};
export const GHOST = (
const GHOST = (
<svg
width="56"
height="85"
@@ -1,108 +0,0 @@
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,6 +1,5 @@
export const Routes = {
HOME: '/',
RESTRICTED: '/restricted',
TX: 'txs',
BLOCKS: 'blocks',
PARTIES: 'parties',
@@ -30,7 +30,6 @@ import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer';
import { FLAGS } from '@vegaprotocol/environment';
import RestrictedPage from './restricted';
export type Navigable = {
path: string;
@@ -357,14 +356,6 @@ export const routerConfig: Route[] = [
...validators,
],
},
{
path: Routes.RESTRICTED,
element: <RestrictedPage />,
handle: {
name: t('Restricted'),
text: t('Restricted'),
},
},
];
export const router = createBrowserRouter(routerConfig);
-12
View File
@@ -106,7 +106,6 @@ const Web3Container = ({
useEthWithdrawApprovalsManager();
return null;
};
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
store.connectors,
store.initialize,
@@ -240,9 +239,6 @@ const AppContainer = () => {
store.setDialogOpen,
]);
// Hacky skip all the loading & web3 init for geo restricted users
const isRestricted = document?.location?.pathname?.includes('/restricted');
useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') {
Sentry.init({
@@ -308,14 +304,6 @@ const AppContainer = () => {
}
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
if (isRestricted) {
return (
<Router>
<AppRouter />
</Router>
);
}
return (
<Router>
<ScrollToTop />
@@ -9,7 +9,6 @@
"pageTitleRedemptionTranche": "Redeem from Tranche",
"pageTitleTranches": "Vesting tranches",
"pageTitle404": "Page not found",
"pageTitle451": "451 unavailable",
"pageTitleNotPermitted": "Can not proceed!",
"pageTitleDisassociate": "Disassociate $VEGA tokens from a Vega key",
"pageTitleProposals": "Proposals",
@@ -12,19 +12,18 @@ import {
generateProposal,
generateYesVotes,
} from '../../test-helpers/generate-proposals';
import { ProposalHeader, NewTransferSummary } from './proposal-header';
import { ProposalHeader } from './proposal-header';
import {
lastWeek,
nextWeek,
mockWalletContext,
createUserVoteQueryMock,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { FLAGS } from '@vegaprotocol/environment';
import { BrowserRouter } from 'react-router-dom';
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.requireActual('@vegaprotocol/proposals'),
@@ -32,7 +31,6 @@ jest.mock('@vegaprotocol/proposals', () => ({
code: 'PARENT_CODE',
parentMarketId: 'PARENT_ID',
}),
useNewTransferProposalDetails: jest.fn(),
}));
const renderComponent = (
@@ -420,78 +418,3 @@ describe('Proposal header', () => {
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();
});
});
@@ -237,11 +237,7 @@ export const ProposalHeader = ({
);
};
export const SuccessorCode = ({
proposalId,
}: {
proposalId?: string | null;
}) => {
const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
const { t } = useTranslation();
const successor = useSuccessorMarketProposalDetails(proposalId);
@@ -258,11 +254,7 @@ export const SuccessorCode = ({
) : null;
};
export const NewTransferSummary = ({
proposalId,
}: {
proposalId?: string | null;
}) => {
const NewTransferSummary = ({ proposalId }: { proposalId?: string | null }) => {
const { t } = useTranslation();
const details = useNewTransferProposalDetails(proposalId);
@@ -273,21 +265,14 @@ export const NewTransferSummary = ({
{GovernanceTransferKindMapping[details.kind.__typename]}{' '}
{t('transfer from')}{' '}
<Lozenge>
{details.source
? truncateMiddle(details.source)
: t(details.sourceType)}
{truncateMiddle(details.source) || t(details.sourceType)}
</Lozenge>{' '}
{t('to')}{' '}
<Lozenge>
{details.destination
? truncateMiddle(details.destination)
: t(details.destinationType)}
</Lozenge>
{t('to')} <Lozenge>{truncateMiddle(details.destination)}</Lozenge>
</span>
);
};
export const CancelTransferSummary = ({
const CancelTransferSummary = ({
proposalId,
}: {
proposalId?: string | null;
File diff suppressed because one or more lines are too long
@@ -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 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: any, 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 }> } } };
@@ -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', windowLength: number, endOfProgram: any, 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`
fragment NewMarketProductFields on Proposal {
@@ -142,12 +142,6 @@ export const generateYesVotes = (
})
.toString(),
},
vestingBalancesSummary: {
__typename: 'PartyVestingBalancesSummary',
epoch: null,
lockedBalances: [],
vestingBalances: [],
},
},
datetime: faker.date.past().toISOString(),
};
@@ -198,12 +192,6 @@ export const generateNoVotes = (
})
.toString(),
},
vestingBalancesSummary: {
__typename: 'PartyVestingBalancesSummary',
epoch: null,
lockedBalances: [],
vestingBalances: [],
},
},
datetime: faker.date.past().toISOString(),
};
@@ -1,112 +0,0 @@
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;
+1 -7
View File
@@ -4,7 +4,6 @@ import Home from './token';
import NotFound from './not-found';
import NotPermitted from './not-permitted';
import Routes from './routes';
import Restricted from './restricted';
const LazyTranches = React.lazy(
() =>
@@ -361,14 +360,9 @@ const routerConfig = [
path: Routes.DISCLAIMER,
element: <LazyDisclaimer name="Disclaimer" />,
},
{
path: Routes.RESTRICTED,
// 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
// Not lazy as loaded when a user first hits the site
element: <NotFound name="NotFound" />,
},
...redirects,
-1
View File
@@ -7,7 +7,6 @@ const Routes = {
PROPOSALS_REJECTED: '/proposals/rejected',
PROTOCOL_UPGRADES: '/protocol-upgrades',
NOT_PERMITTED: '/not-permitted',
RESTRICTED: '/restricted',
NOT_FOUND: '/not-found',
CONTRACTS: '/contracts',
TOKEN: '/token',
@@ -69,7 +69,7 @@ describe(
cy.contains('Something went wrong').should('not.exist');
cy.contains('Application error').should('not.exist');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="partyId"]').eq(1).should('not.be.empty');
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
});
});
}
@@ -12,7 +12,8 @@ const marketSummaryBlock = 'header-summary';
const itemValue = 'item-value';
const itemHeader = 'item-header';
const colCommitmentAmount = '[col-id="commitmentAmount"]';
const colEquityLikeShare = '[col-id="feeShare.equityLikeShare"]';
const colAverageEntryValuation = '[col-id="averageEntryValuation"]';
const colEquityLikeShare = '[col-id="equityLikeShare"]';
const colFee = '[col-id="fee"]';
const colCommitmentAmount_1 = '[col-id="commitmentAmount_1"]';
const colBalance = '[col-id="balance"]';
@@ -68,8 +69,11 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
// 5002-LIQP-002
cy.get(rowSelector)
.first()
.find('[col-id="partyId"]')
.should('have.text', '69464e…dc6f');
.find('[col-id="party.id"]')
.should(
'have.text',
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'
);
cy.get(rowSelector)
.first()
@@ -83,6 +87,12 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
cy.get(rowSelector).first().find(colFee).should('have.text', '0.09%');
// 5002-LIQP-013
cy.get(rowSelector)
.first()
.find(colAverageEntryValuation)
.should('have.text', '685,852.93692');
cy.get(rowSelector)
.first()
.find(colCommitmentAmount_1)
@@ -99,8 +109,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
cy.get(rowSelector).first().find(colCreatedAt).should('not.be.empty');
cy.get(rowSelector).first().find(colUpdatedAt).should('not.be.empty');
});
it('liquidity status column should be sorted properly', () => {
it.skip('liquidity status column should be sorted properly', () => {
// 5002-LIQP-003
const liquidityColDefault = ['Active', 'Pending'];
const liquidityColAsc = ['Active', 'Pending'];
@@ -216,8 +225,11 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
// 5002-LIQP-011
cy.get(rowSelectorLiquidityActive)
.first()
.find('[col-id="partyId"]')
.should('have.text', '69464e…dc6f');
.find('[col-id="party.id"]')
.should(
'have.text',
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'
);
cy.get(rowSelectorLiquidityActive)
.first()
@@ -234,6 +246,12 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
.find(colFee)
.should('have.text', '0.09%');
// 5002-LIQP-013
cy.get(rowSelectorLiquidityActive)
.first()
.find(colAverageEntryValuation)
.should('have.text', '685,852.93692');
cy.get(rowSelectorLiquidityActive)
.first()
.find(colCommitmentAmount_1)
@@ -264,8 +282,11 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
cy.getByTestId('Inactive').click();
cy.get(rowSelectorLiquidityInactive)
.first()
.find('[col-id="partyId"]')
.should('have.text', 'cc464e…dc6f');
.find('[col-id="party.id"]')
.should(
'have.text',
'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'
);
cy.get(rowSelectorLiquidityInactive)
.first()
@@ -282,6 +303,11 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
.find(colFee)
.should('have.text', '0.40%');
cy.get(rowSelectorLiquidityInactive)
.first()
.find(colAverageEntryValuation)
.should('have.text', '685,852.93692');
cy.get(rowSelectorLiquidityInactive)
.first()
.find(colCommitmentAmount_1)
@@ -0,0 +1,529 @@
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 -2
View File
@@ -34,7 +34,6 @@ import {
parentMarketIdQuery,
successorMarketIdsQuery,
successorMarketProposalDetailsQuery,
liquidityProvidersQuery,
} from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
@@ -162,7 +161,7 @@ const mockTradingPage = (
aliasGQLQuery(req, 'Trades', tradesQuery());
aliasGQLQuery(req, 'Chart', chartQuery());
aliasGQLQuery(req, 'LiquidityProvisions', liquidityProvisionsQuery());
aliasGQLQuery(req, 'LiquidityProviders', liquidityProvidersQuery());
aliasGQLQuery(req, 'LiquidityProviderFeeShare');
aliasGQLQuery(req, 'Candles', candlesQuery());
aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery());
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
+6 -6
View File
@@ -3,21 +3,21 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
@@ -86,7 +86,6 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
<MarketLiquiditySupplied
marketId={market.id}
assetDecimals={asset?.decimals || 0}
quantum={asset.quantum}
/>
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
<HeaderStat
@@ -123,7 +123,7 @@ const MainGrid = memo(
<Tab
id="open-orders"
name={t('Open')}
menu={<TradingViews.activeOrders.menu />}
menu={<TradingViews.activeOrders.menu marketId={marketId} />}
>
<TradingViews.orders.component filter={Filter.Open} />
</Tab>
@@ -136,7 +136,7 @@ const MainGrid = memo(
<Tab
id="orders"
name={t('All')}
menu={<TradingViews.orders.menu />}
menu={<TradingViews.orders.menu marketId={marketId} />}
>
<TradingViews.orders.component />
</Tab>
@@ -43,7 +43,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
return (
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu />
<Menu marketId={market?.id || ''} />
</div>
);
}
@@ -319,8 +319,7 @@ describe('Closed', () => {
);
});
// eslint-disable-next-line jest/no-disabled-tests
it.skip('successor marked should be visible', async () => {
it('successor marked should be visible', async () => {
const marketsWithSuccessorID = [
{
__typename: 'MarketEdge' as const,
@@ -0,0 +1,39 @@
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
}
}
}
}
@@ -0,0 +1,117 @@
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>;
@@ -0,0 +1,349 @@
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} />;
};
@@ -15,6 +15,7 @@ import { PositionsMenu } from '../../components/positions-menu';
import { WithdrawalsContainer } from '../../components/withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import { LedgerContainer } from '../../components/ledger-container';
import { AccountHistoryContainer } from './account-history-container';
import {
ResizableGrid,
ResizableGridPanel,
@@ -65,7 +66,10 @@ export const Portfolio = () => {
<ResizableGrid vertical onChange={handleOnLayoutChange}>
<ResizableGridPanel minSize={75}>
<PortfolioGridChild>
<Tabs storageKey="console-portfolio-top-1">
<Tabs storageKey="console-portfolio-top">
<Tab id="account-history" name={t('Account history')}>
<AccountHistoryContainer />
</Tab>
<Tab
id="positions"
name={t('Positions')}
@@ -1,42 +1,21 @@
import {
Input,
InputError,
Loader,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import type { FieldValues } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import classNames from 'classnames';
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
import { Navigate, useSearchParams } from 'react-router-dom';
import { useEffect, useRef, useState } from 'react';
import { RainbowButton } from './buttons';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button } from './buttons';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
import { Routes } from '../../lib/links';
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
import { t } from '@vegaprotocol/i18n';
import { Statistics } from './referral-statistics';
const RELOAD_DELAY = 3000;
const validateCode = (value: string) => {
const number = +`0x${value}`;
if (!value || value.length !== 64) {
return t('Code must be 64 characters in length');
} else if (Number.isNaN(number)) {
return t('Code must be be valid hex');
}
return true;
};
export const ApplyCodeForm = () => {
const navigate = useNavigate();
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const [status, setStatus] = useState<
'requested' | 'failed' | 'successful' | null
>(null);
@@ -48,17 +27,11 @@ export const ApplyCodeForm = () => {
formState: { errors },
setValue,
setError,
watch,
} = useForm();
const [params] = useSearchParams();
const { data: referee } = useReferral({ pubKey, role: 'referee' });
const { data: referrer } = useReferral({ pubKey, role: 'referrer' });
const codeField = watch('code');
const { data: previewData, loading: previewLoading } = useReferral({
code: validateCode(codeField) ? codeField : undefined,
});
const { data: referee } = useReferral(pubKey, 'referee');
const { data: referrer } = useReferral(pubKey, 'referrer');
useEffect(() => {
const code = params.get('code');
@@ -81,7 +54,7 @@ export const ApplyCodeForm = () => {
if (!res) {
setError('code', {
type: 'required',
message: t('The transaction could not be sent'),
message: 'The transaction could not be sent',
});
}
if (res) {
@@ -92,13 +65,9 @@ export const ApplyCodeForm = () => {
if (err.message.includes('user rejected')) {
setStatus(null);
} else {
setStatus(null);
setError('code', {
type: 'required',
message:
err instanceof Error
? err.message
: t('Your code has been rejected'),
message: 'Your code has been rejected',
});
}
});
@@ -130,21 +99,10 @@ export const ApplyCodeForm = () => {
}),
});
// go to main page when successfully applied
useEffect(() => {
if (status === 'successful') {
setTimeout(() => {
navigate(Routes.REFERRALS);
}, RELOAD_DELAY);
}
}, [navigate, status]);
// go to main page if the current pubkey is already a referrer or referee
if (referee || referrer) {
return <Navigate to={Routes.REFERRALS} />;
}
// show "code applied" message when successfully applied
if (status === 'successful') {
return (
<div className="w-1/2 mx-auto">
@@ -152,94 +110,61 @@ export const ApplyCodeForm = () => {
<span className="text-vega-green-500">
<VegaIcon name={VegaIconNames.TICK} size={20} />
</span>{' '}
<span className="pt-1">{t('Code applied')}</span>
<span className="pt-1">Code applied</span>
</h3>
</div>
);
}
const getButtonProps = () => {
if (!pubKey) {
return {
disabled: false,
children: t('Connect wallet'),
type: 'button' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
onClick: ((event) => {
event.preventDefault();
openWalletDialog();
}) as MouseEventHandler,
};
}
if (isReadOnly) {
if (isReadOnly || !pubKey) {
return {
disabled: true,
children: t('Apply a code'),
type: 'submit' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
children: 'Apply',
};
}
if (status === 'requested') {
return {
disabled: true,
children: t('Confirm in wallet...'),
type: 'submit' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
children: 'Confirm in wallet...',
};
}
return {
disabled: false,
children: t('Apply a code'),
type: 'submit' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
children: 'Apply',
};
};
return (
<>
<div className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg">
<h3 className="mb-4 text-2xl text-center calt">
{t('Apply a referral code')}
</h3>
<p className="mb-4 text-center text-base">
{t('Enter a referral code to get trading discounts.')}
</p>
<form
className={classNames('w-full flex flex-col gap-4', {
'animate-shake': Boolean(errors.code),
})}
onSubmit={handleSubmit(onSubmit)}
>
<label>
<span className="sr-only">{t('Your referral code')}</span>
<Input
hasError={Boolean(errors.code)}
{...register('code', {
required: t('You have to provide a code to apply it.'),
validate: validateCode,
})}
placeholder="Enter a code"
className="mb-2 bg-vega-clight-900 dark:bg-vega-cdark-700"
/>
</label>
<RainbowButton variant="border" {...getButtonProps()} />
</form>
{errors.code && (
<InputError className="break-words overflow-auto">
{errors.code.message?.toString()}
</InputError>
)}
</div>
{previewLoading && !previewData ? (
<div className="mt-10">
<Loader />
</div>
) : null}
{previewData ? (
<div className="mt-10">
<h2 className="text-2xl mb-5">{t('You are joining')}</h2>
<Statistics data={previewData} as="referee" />
</div>
) : null}
</>
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt">
Apply a referral code
</h3>
<p className="mb-6 text-center">Enter a referral code</p>
<form
className={classNames('w-full flex flex-col gap-3', {
'animate-shake': Boolean(errors.code),
})}
onSubmit={handleSubmit(onSubmit)}
>
<label className="flex-grow">
<span className="block mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Your referral code
</span>
<Input
hasError={Boolean(errors.code)}
{...register('code', {
required: 'You have to provide a code to apply it.',
})}
/>
</label>
<Button className="w-full" type="submit" {...getButtonProps()} />
</form>
{errors.code && (
<InputError>{errors.code.message?.toString()}</InputError>
)}
</div>
);
};
@@ -16,23 +16,20 @@ export const RainbowButton = ({
}: RainbowButtonProps & ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
className={classNames(
'bg-rainbow rounded-lg overflow-hidden disabled:opacity-40',
'hover:bg-rainbow-180 hover:animate-spin-rainbow',
'bg-rainbow hover:bg-none hover:bg-rainbow enabled:hover:bg-vega-pink-500 rounded-lg overflow-hidden disabled:opacity-40',
{
'px-5 py-3 text-white': variant === 'full',
'p-[0.125rem]': variant === 'border',
}
},
className
)}
{...props}
>
<div
className={classNames(
{
'bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white px-5 py-3 rounded-[0.35rem] overflow-hidden':
variant === 'border',
},
className
)}
className={classNames({
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white px-5 py-3 rounded-[0.35rem] overflow-hidden':
variant === 'border',
})}
>
{children}
</div>
@@ -58,20 +55,6 @@ const DISABLED_RAINBOW_TAB_STYLE = classNames(
'[&.active]:text-white'
);
const TAB_STYLE = classNames(
'inline-block',
'bg-transparent',
'text-vega-clight-200 dark:text-vega-cdark-200',
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100',
'data-[state="active"]:text-black dark:data-[state="active"]:text-white',
'data-[state="active"]:border-b-2 data-[state="active"]:border-b-black dark:data-[state="active"]:border-b-white',
'[&.active]:text-black dark:[&.active]:text-white',
'[&.active]:border-b-2 [&.active]:border-b-black dark:[&.active]:border-b-white',
'mx-4 px-0 py-3',
'uppercase'
);
const DISABLED_TAB_STYLE = classNames('pointer-events-none');
export const RainbowTabButton = forwardRef<
HTMLButtonElement,
{ disabled?: boolean } & ButtonHTMLAttributes<HTMLButtonElement>
@@ -110,26 +93,6 @@ export const RainbowTabLink = ({
</NavLink>
);
export const TabLink = ({
to,
children,
className,
disabled = false,
...props
}: { disabled?: boolean } & ComponentProps<typeof NavLink>) => (
<NavLink
to={to}
className={classNames(
TAB_STYLE,
disabled && DISABLED_TAB_STYLE,
typeof className === 'string' ? className : undefined
)}
{...props}
>
{children}
</NavLink>
);
export const Button = forwardRef<
HTMLButtonElement,
ComponentProps<typeof TradingButton>
@@ -4,8 +4,3 @@ export const GRADIENT =
export const SKY_BACKGROUND =
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
// TODO: Update the links to use the correct referral related pages
export const REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
export const ABOUT_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
export const DISCLAIMER_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
@@ -19,58 +19,70 @@ import {
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
import { useStakeAvailable } from './hooks/use-stake-available';
import {
ABOUT_REFERRAL_DOCS_LINK,
DISCLAIMER_REFERRAL_DOCS_LINK,
} from './constants';
import { useReferral } from './hooks/use-referral';
import { t } from '@vegaprotocol/i18n';
export const CreateCodeContainer = () => {
return <CreateCodeForm />;
const { stakeAvailable, requiredStake } = useStakeAvailable();
if (stakeAvailable == null || requiredStake == null) {
return null;
}
return (
<CreateCodeForm
currentStakeAvailable={stakeAvailable}
requiredStake={requiredStake}
/>
);
};
export const CreateCodeForm = () => {
export const CreateCodeForm = ({
currentStakeAvailable,
requiredStake,
}: {
currentStakeAvailable: bigint;
requiredStake: bigint;
}) => {
const [dialogOpen, setDialogOpen] = useState(false);
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { pubKey, isReadOnly } = useVegaWallet();
const { pubKey } = useVegaWallet();
return (
<div className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg">
<h3 className="mb-4 text-2xl text-center calt">
{t('Create a referral code')}
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt">
Create a referral code
</h3>
<p className="mb-4 text-center text-base">
{t(
'Generate a referral code to share with your friends and start earning commission.'
)}
<p className="mb-6 text-center">
Generate a referral code to share with your friends and start earning
commission.
</p>
<div className="w-full flex flex-col">
<RainbowButton
variant="border"
disabled={isReadOnly}
onClick={() => {
if (pubKey) {
setDialogOpen(true);
} else {
openWalletDialog();
}
}}
>
{pubKey ? t('Create a referral code') : t('Connect wallet')}
</RainbowButton>
<div className="mb-5">
<div className="text-center">
<RainbowButton
variant="border"
onClick={() => {
if (pubKey) {
setDialogOpen(true);
} else {
openWalletDialog();
}
}}
>
{pubKey ? 'Create a referral code' : 'Connect wallet'}
</RainbowButton>
</div>
</div>
<Dialog
title={t('Create a referral code')}
title="Create a referral code"
open={dialogOpen}
onChange={() => setDialogOpen(false)}
size="small"
>
<CreateCodeDialog setDialogOpen={setDialogOpen} />
<CreateCodeDialog
currentStakeAvailable={currentStakeAvailable}
setDialogOpen={setDialogOpen}
requiredStake={requiredStake}
/>
</Dialog>
</div>
);
@@ -78,21 +90,21 @@ export const CreateCodeForm = () => {
const CreateCodeDialog = ({
setDialogOpen,
currentStakeAvailable,
requiredStake,
}: {
setDialogOpen: (open: boolean) => void;
currentStakeAvailable: bigint;
requiredStake: bigint;
}) => {
const createLink = useLinks(DApp.Governance);
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const { refetch } = useReferral({ pubKey, role: 'referrer' });
const [err, setErr] = useState<string | null>(null);
const [code, setCode] = useState<string | null>(null);
const [status, setStatus] = useState<
'idle' | 'loading' | 'success' | 'error'
>('idle');
const { stakeAvailable: currentStakeAvailable, requiredStake } =
useStakeAvailable();
const onSubmit = () => {
if (isReadOnly || !pubKey) {
setErr('Not connected');
@@ -128,60 +140,45 @@ const CreateCodeDialog = ({
const getButtonProps = () => {
if (status === 'idle' || status === 'error') {
return {
children: t('Generate code'),
children: 'Generate code',
onClick: () => onSubmit(),
};
}
if (status === 'loading') {
return {
children: t('Confirm in wallet...'),
children: 'Confirm in wallet...',
disabled: true,
};
}
if (status === 'success') {
return {
children: t('Close'),
children: 'Close',
intent: Intent.Success,
onClick: () => {
refetch();
setDialogOpen(false);
},
onClick: () => setDialogOpen(false),
};
}
};
if (!pubKey || currentStakeAvailable == null || requiredStake == null) {
return (
<div className="flex flex-col gap-4">
<p>{t('You must be connected to the Vega wallet.')}</p>
<TradingButton
intent={Intent.Primary}
onClick={() => setDialogOpen(false)}
>
{t('Close')}
</TradingButton>
</div>
);
}
if (currentStakeAvailable < requiredStake) {
// TODO: Add when network parameters are updated
if (
currentStakeAvailable === BigInt(0) ||
currentStakeAvailable < requiredStake
) {
return (
<div className="flex flex-col gap-4">
<p>
{t('You need at least')}{' '}
{addDecimalsFormatNumber(requiredStake.toString(), 18)}{' '}
{t(
'VEGA staked to generate a referral code and participate in the referral program.'
)}
You need at least{' '}
{addDecimalsFormatNumber(requiredStake.toString(), 18)} VEGA staked to
generate a referral code and participate in the referral program.
</p>
<TradingAnchorButton
href={createLink(TokenStaticLinks.ASSOCIATE)}
intent={Intent.Primary}
target="_blank"
>
{t('Stake some $VEGA now')}
Stake some $VEGA now
</TradingAnchorButton>
</div>
);
@@ -191,9 +188,8 @@ const CreateCodeDialog = ({
<div className="flex flex-col gap-4">
{(status === 'idle' || status === 'loading' || status === 'error') && (
<p>
{t(
'Generate a referral code to share with your friends and start earning commission.'
)}
Generate a referral code to share with your friends and start earning
commission.
</p>
)}
{status === 'success' && code && (
@@ -208,7 +204,7 @@ const CreateCodeDialog = ({
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>{t('Copy')}</span>
<span>Copy</span>
</TradingButton>
</CopyWithTooltip>
</div>
@@ -219,13 +215,10 @@ const CreateCodeDialog = ({
{...getButtonProps()}
/>
{err && <InputError>{err}</InputError>}
{/* TODO: Add links */}
<div className="flex justify-center pt-5 mt-2 text-sm border-t gap-4 text-default border-default">
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
{t('About the referral program')}
</ExternalLink>
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
{t('Disclaimer')}
</ExternalLink>
<ExternalLink>About the referral program</ExternalLink>
<ExternalLink>Disclaimer</ExternalLink>
</div>
</div>
);
@@ -3,7 +3,6 @@ import { RainbowButton } from './buttons';
import { AnimatedDudeWithWire } from './graphics/dude';
import { LayoutWithSky } from './layout';
import { Routes } from '../../lib/links';
import { t } from '@vegaprotocol/i18n';
export const ErrorBoundary = () => {
const error = useRouteError();
@@ -40,7 +39,7 @@ export const ErrorBoundary = () => {
variant="border"
className="text-xs"
>
{t('Go back and try again')}
Go back and try again
</RainbowButton>
</p>
</LayoutWithSky>
@@ -61,7 +60,7 @@ export const NotFound = () => {
<h1 className="text-6xl font-alpha calt mb-10">{'Not found'}</h1>
<p className="text-lg mb-10">
{t("The page you're looking for doesn't exists.")}
{"The page you're looking for doesn't exists."}
</p>
<p className="text-lg mb-10">
@@ -70,7 +69,7 @@ export const NotFound = () => {
variant="border"
className="text-xs"
>
{t('Go back and try again')}
Go back and try again
</RainbowButton>
</p>
</div>
@@ -1,19 +0,0 @@
query ReferralProgram {
currentReferralProgram {
id
version
endOfProgramTimestamp
windowLength
endedAt
benefitTiers {
minimumEpochs
minimumRunningNotionalTakerVolume
referralDiscountFactor
referralRewardFactor
}
stakingTiers {
minimumStakedTokens
referralRewardMultiplier
}
}
}
@@ -1,9 +0,0 @@
query CurrentEpochInfo {
epoch {
id
timestamps {
start
end
}
}
}
@@ -1,14 +0,0 @@
query Referees($code: ID!, $aggregationDays: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) {
edges {
node {
referralSetId
refereeId
joinedAt
atEpoch
totalRefereeNotionalTakerVolume
totalRefereeGeneratedRewards
}
}
}
}
@@ -1,16 +0,0 @@
query ReferralSetStats($code: ID!, $epoch: Int) {
referralSetStats(setId: $code, epoch: $epoch) {
edges {
node {
atEpoch
partyId
discountFactor
rewardFactor
epochNotionalTakerVolume
referralSetRunningNotionalTakerVolume
rewardsMultiplier
rewardsFactorMultiplier
}
}
}
}
@@ -1,12 +0,0 @@
query ReferralSets($id: ID, $referrer: ID, $referee: ID) {
referralSets(id: $id, referrer: $referrer, referee: $referee) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
@@ -1,59 +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 ReferralProgramQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ReferralProgramQuery = { __typename?: 'Query', currentReferralProgram?: { __typename?: 'CurrentReferralProgram', id: string, version: number, endOfProgramTimestamp: any, windowLength: number, endedAt?: any | null, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | null };
export const ReferralProgramDocument = gql`
query ReferralProgram {
currentReferralProgram {
id
version
endOfProgramTimestamp
windowLength
endedAt
benefitTiers {
minimumEpochs
minimumRunningNotionalTakerVolume
referralDiscountFactor
referralRewardFactor
}
stakingTiers {
minimumStakedTokens
referralRewardMultiplier
}
}
}
`;
/**
* __useReferralProgramQuery__
*
* To run a query within a React component, call `useReferralProgramQuery` and pass it any options that fit your needs.
* When your component renders, `useReferralProgramQuery` 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 } = useReferralProgramQuery({
* variables: {
* },
* });
*/
export function useReferralProgramQuery(baseOptions?: Apollo.QueryHookOptions<ReferralProgramQuery, ReferralProgramQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ReferralProgramQuery, ReferralProgramQueryVariables>(ReferralProgramDocument, options);
}
export function useReferralProgramLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ReferralProgramQuery, ReferralProgramQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ReferralProgramQuery, ReferralProgramQueryVariables>(ReferralProgramDocument, options);
}
export type ReferralProgramQueryHookResult = ReturnType<typeof useReferralProgramQuery>;
export type ReferralProgramLazyQueryHookResult = ReturnType<typeof useReferralProgramLazyQuery>;
export type ReferralProgramQueryResult = Apollo.QueryResult<ReferralProgramQuery, ReferralProgramQueryVariables>;
@@ -1,49 +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 CurrentEpochInfoQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type CurrentEpochInfoQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null } } };
export const CurrentEpochInfoDocument = gql`
query CurrentEpochInfo {
epoch {
id
timestamps {
start
end
}
}
}
`;
/**
* __useCurrentEpochInfoQuery__
*
* To run a query within a React component, call `useCurrentEpochInfoQuery` and pass it any options that fit your needs.
* When your component renders, `useCurrentEpochInfoQuery` 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 } = useCurrentEpochInfoQuery({
* variables: {
* },
* });
*/
export function useCurrentEpochInfoQuery(baseOptions?: Apollo.QueryHookOptions<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>(CurrentEpochInfoDocument, options);
}
export function useCurrentEpochInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>(CurrentEpochInfoDocument, options);
}
export type CurrentEpochInfoQueryHookResult = ReturnType<typeof useCurrentEpochInfoQuery>;
export type CurrentEpochInfoLazyQueryHookResult = ReturnType<typeof useCurrentEpochInfoLazyQuery>;
export type CurrentEpochInfoQueryResult = Apollo.QueryResult<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>;
@@ -1,59 +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 RefereesQueryVariables = Types.Exact<{
code: Types.Scalars['ID'];
aggregationDays?: Types.InputMaybe<Types.Scalars['Int']>;
}>;
export type RefereesQuery = { __typename?: 'Query', referralSetReferees: { __typename?: 'ReferralSetRefereeConnection', edges: Array<{ __typename?: 'ReferralSetRefereeEdge', node: { __typename?: 'ReferralSetReferee', referralSetId: string, refereeId: string, joinedAt: any, atEpoch: number, totalRefereeNotionalTakerVolume: string, totalRefereeGeneratedRewards: string } } | null> } };
export const RefereesDocument = gql`
query Referees($code: ID!, $aggregationDays: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) {
edges {
node {
referralSetId
refereeId
joinedAt
atEpoch
totalRefereeNotionalTakerVolume
totalRefereeGeneratedRewards
}
}
}
}
`;
/**
* __useRefereesQuery__
*
* To run a query within a React component, call `useRefereesQuery` and pass it any options that fit your needs.
* When your component renders, `useRefereesQuery` 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 } = useRefereesQuery({
* variables: {
* code: // value for 'code'
* aggregationDays: // value for 'aggregationDays'
* },
* });
*/
export function useRefereesQuery(baseOptions: Apollo.QueryHookOptions<RefereesQuery, RefereesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<RefereesQuery, RefereesQueryVariables>(RefereesDocument, options);
}
export function useRefereesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<RefereesQuery, RefereesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<RefereesQuery, RefereesQueryVariables>(RefereesDocument, options);
}
export type RefereesQueryHookResult = ReturnType<typeof useRefereesQuery>;
export type RefereesLazyQueryHookResult = ReturnType<typeof useRefereesLazyQuery>;
export type RefereesQueryResult = Apollo.QueryResult<RefereesQuery, RefereesQueryVariables>;
@@ -1,61 +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 ReferralSetStatsQueryVariables = Types.Exact<{
code: Types.Scalars['ID'];
epoch?: Types.InputMaybe<Types.Scalars['Int']>;
}>;
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string } } | null> } };
export const ReferralSetStatsDocument = gql`
query ReferralSetStats($code: ID!, $epoch: Int) {
referralSetStats(setId: $code, epoch: $epoch) {
edges {
node {
atEpoch
partyId
discountFactor
rewardFactor
epochNotionalTakerVolume
referralSetRunningNotionalTakerVolume
rewardsMultiplier
rewardsFactorMultiplier
}
}
}
}
`;
/**
* __useReferralSetStatsQuery__
*
* To run a query within a React component, call `useReferralSetStatsQuery` and pass it any options that fit your needs.
* When your component renders, `useReferralSetStatsQuery` 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 } = useReferralSetStatsQuery({
* variables: {
* code: // value for 'code'
* epoch: // value for 'epoch'
* },
* });
*/
export function useReferralSetStatsQuery(baseOptions: Apollo.QueryHookOptions<ReferralSetStatsQuery, ReferralSetStatsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ReferralSetStatsQuery, ReferralSetStatsQueryVariables>(ReferralSetStatsDocument, options);
}
export function useReferralSetStatsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ReferralSetStatsQuery, ReferralSetStatsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ReferralSetStatsQuery, ReferralSetStatsQueryVariables>(ReferralSetStatsDocument, options);
}
export type ReferralSetStatsQueryHookResult = ReturnType<typeof useReferralSetStatsQuery>;
export type ReferralSetStatsLazyQueryHookResult = ReturnType<typeof useReferralSetStatsLazyQuery>;
export type ReferralSetStatsQueryResult = Apollo.QueryResult<ReferralSetStatsQuery, ReferralSetStatsQueryVariables>;
@@ -1,59 +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 ReferralSetsQueryVariables = Types.Exact<{
id?: Types.InputMaybe<Types.Scalars['ID']>;
referrer?: Types.InputMaybe<Types.Scalars['ID']>;
referee?: Types.InputMaybe<Types.Scalars['ID']>;
}>;
export type ReferralSetsQuery = { __typename?: 'Query', referralSets: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', id: string, referrer: string, createdAt: any, updatedAt: any } } | null> } };
export const ReferralSetsDocument = gql`
query ReferralSets($id: ID, $referrer: ID, $referee: ID) {
referralSets(id: $id, referrer: $referrer, referee: $referee) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
`;
/**
* __useReferralSetsQuery__
*
* To run a query within a React component, call `useReferralSetsQuery` and pass it any options that fit your needs.
* When your component renders, `useReferralSetsQuery` 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 } = useReferralSetsQuery({
* variables: {
* id: // value for 'id'
* referrer: // value for 'referrer'
* referee: // value for 'referee'
* },
* });
*/
export function useReferralSetsQuery(baseOptions?: Apollo.QueryHookOptions<ReferralSetsQuery, ReferralSetsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ReferralSetsQuery, ReferralSetsQueryVariables>(ReferralSetsDocument, options);
}
export function useReferralSetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ReferralSetsQuery, ReferralSetsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ReferralSetsQuery, ReferralSetsQueryVariables>(ReferralSetsDocument, options);
}
export type ReferralSetsQueryHookResult = ReturnType<typeof useReferralSetsQuery>;
export type ReferralSetsLazyQueryHookResult = ReturnType<typeof useReferralSetsLazyQuery>;
export type ReferralSetsQueryResult = Apollo.QueryResult<ReferralSetsQuery, ReferralSetsQueryVariables>;
@@ -1,8 +1,32 @@
import { gql, useQuery } from '@apollo/client';
import { getNumberFormat } from '@vegaprotocol/utils';
import { addDays } from 'date-fns';
import sortBy from 'lodash/sortBy';
import omit from 'lodash/omit';
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
// TODO: Generate query
// eslint-disable-next-line
const REFERRAL_PROGRAM_QUERY = gql`
query ReferralProgram {
currentReferralProgram {
id
version
endOfProgramTimestamp
windowLength
endedAt
benefitTiers {
minimumEpochs
minimumRunningNotionalTakerVolume
referralDiscountFactor
referralRewardFactor
}
stakingTiers {
minimumStakedTokens
referralRewardMultiplier
}
}
}
`;
const STAKING_TIERS_MAPPING: Record<number, string> = {
1: 'Tradestarter',
@@ -59,11 +83,11 @@ const MOCK = {
};
export const useReferralProgram = () => {
const { data, loading, error } = useReferralProgramQuery({
const { data, loading, error } = useQuery(REFERRAL_PROGRAM_QUERY, {
fetchPolicy: 'cache-and-network',
});
if (!data || !data.currentReferralProgram) {
if (!data) {
return {
benefitTiers: [],
stakingTiers: [],
@@ -80,15 +104,11 @@ export const useReferralProgram = () => {
.map((t, i) => {
return {
tier: i + 1,
rewardFactor: Number(t.referralRewardFactor),
commission: Number(t.referralRewardFactor) * 100 + '%',
discountFactor: Number(t.referralDiscountFactor),
discount: Number(t.referralDiscountFactor) * 100 + '%',
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
volume: getNumberFormat(0).format(
Number(t.minimumRunningNotionalTakerVolume)
),
epochs: Number(t.minimumEpochs),
};
});
@@ -103,16 +123,10 @@ export const useReferralProgram = () => {
};
});
const details = omit(
data.currentReferralProgram,
'benefitTiers',
'stakingTiers'
);
return {
benefitTiers,
stakingTiers,
details,
details: omit(data.currentReferralProgram, 'benefitTiers', 'stakingTiers'),
loading,
error,
};
@@ -1,116 +1,114 @@
import { gql, useQuery } from '@apollo/client';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useCallback } from 'react';
import { useRefereesQuery } from './__generated__/Referees';
import compact from 'lodash/compact';
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
import { useReferralSetsQuery } from './__generated__/ReferralSets';
const DEFAULT_AGGREGATION_DAYS = 30;
const REFERRER_QUERY = gql`
query ReferralSets($partyId: ID!) {
referralSets(referrer: $partyId) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
`;
export type Role = 'referrer' | 'referee';
type UseReferralArgs = (
| { code: string }
| { pubKey: string | null; role: Role }
) & {
aggregationDays?: number;
const REFEREE_QUERY = gql`
query ReferralSets($partyId: ID!) {
referralSets(referee: $partyId) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
`;
const REFEREES_QUERY = gql`
query ReferralSets($code: ID!) {
referralSetReferees(id: $code) {
edges {
node {
referralSetId
refereeId
joinedAt
atEpoch
}
}
}
}
`;
// TODO: generate types after perps work is merged
export type ReferralData = {
code: string;
referees: Array<{
refereeId: string;
joinedAt: string;
atEpoch: number;
}>;
};
const prepareVariables = (
args: UseReferralArgs
): [ReferralSetsQueryVariables, boolean] => {
const byCode = 'code' in args;
const byRole = 'pubKey' in args && 'role' in args;
let variables = {};
let skip = true;
if (byCode) {
variables = {
id: args.code,
};
skip = !args.code;
}
if (byRole) {
if (args.role === 'referee') {
variables = { referee: args.pubKey };
}
if (args.role === 'referrer') {
variables = { referrer: args.pubKey };
}
skip = !args.pubKey;
}
return [variables, skip];
};
export const useReferral = (args: UseReferralArgs) => {
const [variables, skip] = prepareVariables(args);
export const useReferral = (
pubKey: string | null,
role: 'referrer' | 'referee'
) => {
const query = {
referrer: REFERRER_QUERY,
referee: REFEREE_QUERY,
};
const {
data: referralData,
loading: referralLoading,
error: referralError,
refetch: referralRefetch,
} = useReferralSetsQuery({
variables,
skip,
} = useQuery(query[role], {
variables: {
partyId: pubKey,
},
skip: !pubKey,
fetchPolicy: 'cache-and-network',
});
// A user can only have 1 active referral program at a time
const referralSet =
referralData?.referralSets.edges &&
referralData.referralSets.edges.length > 0
? referralData.referralSets.edges[0]?.node
: undefined;
const referral = referralData?.referralSets.edges.length
? referralData.referralSets.edges[0].node
: undefined;
const {
data: refereesData,
loading: refereesLoading,
error: refereesError,
refetch: refereesRefetch,
} = useRefereesQuery({
} = useQuery(REFEREES_QUERY, {
variables: {
code: referralSet?.id as string,
aggregationDays:
args.aggregationDays != null
? args.aggregationDays
: DEFAULT_AGGREGATION_DAYS,
code: referral?.id,
},
skip: !referralSet?.id,
skip: !referral?.id,
fetchPolicy: 'cache-and-network',
context: { isEnlargedTimeout: true },
});
const referees = compact(
removePaginationWrapper(refereesData?.referralSetReferees.edges)
const referees = removePaginationWrapper(
refereesData?.referralSetReferees.edges
);
const refetch = useCallback(() => {
referralRefetch();
refereesRefetch();
}, [refereesRefetch, referralRefetch]);
const byReferee =
'role' in args && 'pubKey' in args && args.role === 'referee';
const referee = byReferee
? referees.find((r) => r.refereeId === args.pubKey) || null
: null;
const data =
referralSet && refereesData
referral && refereesData
? {
code: referralSet.id,
role: 'role' in args ? args.role : null,
referee: referee,
referrerId: referralSet.referrer,
createdAt: referralSet.createdAt,
code: referral.id,
referees,
}
: undefined;
return {
data,
data: data as ReferralData | undefined,
loading: referralLoading || refereesLoading,
error: referralError || refereesError,
refetch,
};
};
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import { Table } from './table';
export const HowItWorksTable = () => (
@@ -14,9 +13,7 @@ export const HowItWorksTable = () => (
1
</span>
),
step: t(
'Referrers generate a code assigned to their key via an on chain transaction'
),
step: 'Referrers generate a code assigned to their key via an on chain transaction',
},
{
number: (
@@ -24,9 +21,7 @@ export const HowItWorksTable = () => (
2
</span>
),
step: t(
'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction'
),
step: 'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction',
},
{
number: (
@@ -34,9 +29,7 @@ export const HowItWorksTable = () => (
3
</span>
),
step: t(
'Discounts are applied automatically during trading based on the key(s) used'
),
step: 'Discounts are applied automatically during trading based on the key(s) used',
},
{
number: (
@@ -44,9 +37,7 @@ export const HowItWorksTable = () => (
4
</span>
),
step: t(
'Referrers earn commission based on a percentage of the taker fees their referees pay'
),
step: 'Referrers earn commission based on a percentage of the taker fees their referees pay',
},
{
number: (
@@ -54,9 +45,7 @@ export const HowItWorksTable = () => (
5
</span>
),
step: t(
'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee'
),
step: 'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee',
},
]}
></Table>
@@ -1,6 +1,5 @@
import classNames from 'classnames';
import { AnimatedDudeWithWire } from './graphics/dude';
import { t } from '@vegaprotocol/i18n';
export const LandingBanner = () => {
return (
@@ -14,17 +13,16 @@ export const LandingBanner = () => {
</div>
<div className="pt-32 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10">
{t('Earn commission & stake rewards')}
Earn commission & stake rewards
</h1>
<p className="text-lg mb-10">
{t(
'Invite friends and earn commission in the form of Vega rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
)}
Invite friends and earn commission in the form of Vega rewards from
the trading fees they pay. Stake those rewards to earn multipliers
on future rewards.
</p>
<p className="text-lg">
{t(
'Any friends that join using the code will receive discounts off trading fees.'
)}
Any friends that join using the code will receive discounts off
trading fees.
</p>
</div>
</div>
@@ -1,45 +1,66 @@
import { CodeTile, StatTile } from './tile';
import { Tile } from './tile';
import {
CopyWithTooltip,
Input,
VegaIcon,
VegaIconNames,
truncateMiddle,
} from '@vegaprotocol/ui-toolkit';
import { Button, RainbowButton } from './buttons';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import type { ReferralData } from './hooks/use-referral';
import { useReferral } from './hooks/use-referral';
import { CreateCodeContainer } from './create-code-form';
import classNames from 'classnames';
import { Table } from './table';
import {
addDecimalsFormatNumber,
getDateFormat,
getDateTimeFormat,
getNumberFormat,
getUserLocale,
removePaginationWrapper,
} from '@vegaprotocol/utils';
import { useReferralSetStatsQuery } from './hooks/__generated__/ReferralSetStats';
import compact from 'lodash/compact';
import { useReferralProgram } from './hooks/use-referral-program';
import { useStakeAvailable } from './hooks/use-stake-available';
import sortBy from 'lodash/sortBy';
import { useLayoutEffect, useRef, useState } from 'react';
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
import BigNumber from 'bignumber.js';
import { t } from '@vegaprotocol/i18n';
const CodeTile = ({
code,
as,
}: {
code: string;
as: 'referrer' | 'referee';
}) => {
return (
<Tile variant="rainbow">
<h3 className="mb-1 text-lg calt">Your referral code</h3>
{as === 'referrer' && (
<p className="mb-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Share this code with friends
</p>
)}
<div className="flex gap-2">
<Input size={1} readOnly value={code} />
<CopyWithTooltip text={code}>
<Button
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>Copy</span>
</Button>
</CopyWithTooltip>
</div>
</Tile>
);
};
export const ReferralStatistics = () => {
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { pubKey } = useVegaWallet();
const { data: referee } = useReferral({
pubKey,
role: 'referee',
});
const { data: referrer } = useReferral({
pubKey,
role: 'referrer',
});
const { data: referee } = useReferral(pubKey, 'referee');
const { data: referrer } = useReferral(pubKey, 'referrer');
if (!pubKey) {
return (
<div className="text-center">
<RainbowButton variant="border" onClick={() => openWalletDialog()}>
Connect wallet
</RainbowButton>
</div>
);
}
if (referee?.code) {
return <Statistics data={referee} as="referee" />;
}
@@ -51,265 +72,42 @@ export const ReferralStatistics = () => {
return <CreateCodeContainer />;
};
export const Statistics = ({
const Statistics = ({
data,
as,
}: {
data: NonNullable<ReturnType<typeof useReferral>['data']>;
data: ReferralData;
as: 'referrer' | 'referee';
}) => {
const { data: epochData } = useCurrentEpochInfoQuery();
const { stakeAvailable } = useStakeAvailable();
const { benefitTiers } = useReferralProgram();
const { data: statsData } = useReferralSetStatsQuery({
variables: {
code: data.code,
},
skip: !data?.code,
fetchPolicy: 'cache-and-network',
});
const currentEpoch = Number(epochData?.epoch.id);
const stats =
statsData?.referralSetStats.edges &&
compact(removePaginationWrapper(statsData.referralSetStats.edges));
const refereeInfo = data.referee;
const refereeStats = stats?.find(
(r) => r.partyId === data.referee?.refereeId
);
const statsAvailable = stats && stats.length > 0 && stats[0];
const baseCommissionValue = statsAvailable
? Number(statsAvailable.rewardFactor)
: 0;
const runningVolumeValue = statsAvailable
? Number(statsAvailable.referralSetRunningNotionalTakerVolume)
: 0;
const multiplier = statsAvailable
? Number(statsAvailable.rewardsMultiplier)
: 1;
const finalCommissionValue = !isNaN(multiplier)
? baseCommissionValue
: multiplier * baseCommissionValue;
const discountFactorValue = refereeStats?.discountFactor
? Number(refereeStats.discountFactor)
: 0;
const currentBenefitTierValue = benefitTiers.find(
(t) =>
!isNaN(discountFactorValue) &&
!isNaN(t.discountFactor) &&
t.discountFactor === discountFactorValue
);
const nextBenefitTierValue =
currentBenefitTierValue &&
benefitTiers.find((t) => t.tier === currentBenefitTierValue.tier - 1);
const epochsValue =
!isNaN(currentEpoch) && refereeInfo?.atEpoch
? currentEpoch - refereeInfo?.atEpoch
: 0;
const nextBenefitTierVolumeValue = nextBenefitTierValue
? nextBenefitTierValue.minimumVolume - runningVolumeValue
: 0;
const nextBenefitTierEpochsValue = nextBenefitTierValue
? nextBenefitTierValue.epochs - epochsValue
: 0;
const baseCommissionTile = (
<StatTile title={t('Base commission rate')}>
{baseCommissionValue * 100}%
</StatTile>
);
const stakingMultiplierTile = (
<StatTile
title={t('Staking multiplier')}
description={`(${addDecimalsFormatNumber(
stakeAvailable?.toString() || 0,
18
)} $VEGA staked)`}
>
{multiplier || t('None')}
</StatTile>
);
const finalCommissionTile = (
<StatTile title={t('Final commission rate')}>
{finalCommissionValue * 100}%
</StatTile>
);
const numberOfTradersValue = data.referees.length;
const numberOfTradersTile = (
<StatTile title={t('Number of traders')}>{numberOfTradersValue}</StatTile>
);
const codeTile = <CodeTile code={data?.code} />;
const createdAtTile = (
<StatTile title={t('Created at')}>
<span className="text-3xl">
{getDateFormat().format(new Date(data.createdAt))}
</span>
</StatTile>
);
const totalCommissionValue = data.referees
.map((r) => new BigNumber(r.totalRefereeGeneratedRewards))
.reduce((all, r) => all.plus(r), new BigNumber(0));
const totalCommissionTile = (
<StatTile
title={t('Total commission (last 30 days)')}
description={t('(qUSD)')}
>
{getNumberFormat(0).format(Number(totalCommissionValue))}
</StatTile>
);
const referrerTiles = (
<>
<div className="grid grid-rows-1 gap-5 grid-cols-1 md:grid-cols-3">
{baseCommissionTile}
{stakingMultiplierTile}
{finalCommissionTile}
</div>
<div className="grid grid-rows-1 gap-5 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
{codeTile}
{createdAtTile}
{numberOfTradersTile}
{totalCommissionTile}
</div>
</>
);
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
});
const currentBenefitTierTile = (
<StatTile title={t('Current tier')}>
{currentBenefitTierValue?.tier || '-'}
</StatTile>
);
const discountFactorTile = (
<StatTile title={t('Discount')}>{discountFactorValue * 100}%</StatTile>
);
const runningVolumeTile = (
<StatTile title={t('Combined volume')}>
{compactNumFormat.format(runningVolumeValue)}
</StatTile>
);
const epochsTile = (
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
);
const nextTierVolumeTile = (
<StatTile title={t('Volume to next tier')}>
{nextBenefitTierVolumeValue <= 0
? '0'
: compactNumFormat.format(nextBenefitTierVolumeValue)}
</StatTile>
);
const nextTierEpochsTile = (
<StatTile title={t('Epochs to next tier')}>
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
</StatTile>
);
const refereeTiles = (
<>
<div className="grid grid-rows-1 gap-5 grid-cols-1 md:grid-cols-3">
{currentBenefitTierTile}
{discountFactorTile}
{codeTile}
</div>
<div className="grid grid-rows-1 gap-5 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
{runningVolumeTile}
{nextTierVolumeTile}
{epochsTile}
{nextTierEpochsTile}
</div>
</>
);
const [collapsed, setCollapsed] = useState(false);
const tableRef = useRef<HTMLTableElement>(null);
useLayoutEffect(() => {
if ((tableRef.current?.getBoundingClientRect().height || 0) > 384) {
setCollapsed(true);
}
}, []);
return (
<>
{/* Stats tiles */}
<div
className={classNames('grid grid-cols-1 grid-rows-1 gap-5 mx-auto', {
'md:w-1/2': as === 'referee',
'md:w-2/3': as === 'referrer',
})}
>
<div
className={classNames(
'grid grid-cols-1 grid-rows-1 gap-5 mx-auto mb-20'
)}
className={classNames('grid grid-rows-1 gap-5', {
'grid-cols-2': as === 'referrer',
'grid-cols-1': as === 'referee',
})}
>
{as === 'referrer' && referrerTiles}
{as === 'referee' && refereeTiles}
{as === 'referrer' && data?.referees && (
<Tile className="py-3 h-full">
<div className="absolute top-1/2 left-1/2 translate-x-[-50%] translate-y-[-50%]">
<h3 className="mb-1 text-6xl text-center">
{data.referees.length}
</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
{data.referees.length === 1
? 'Trader referred'
: 'Total traders referred'}
</p>
</div>
</Tile>
)}
<CodeTile code={data?.code} as={as} />
</div>
{/* Referees (only for referrer view) */}
{as === 'referrer' && data.referees.length > 0 && (
<div className="mt-20 mb-20">
<h2 className="text-2xl mb-5">{t('Referees')}</h2>
<div
className={classNames(
collapsed && [
'relative max-h-96 overflow-hidden',
'after:w-full after:h-20 after:absolute after:bottom-0 after:left-0',
'after:bg-gradient-to-t after:from-white after:dark:from-vega-cdark-900 after:to-transparent',
]
)}
>
<button
className={classNames(
'absolute left-1/2 bottom-0 z-10 p-2 translate-x-[-50%]',
{
hidden: !collapsed,
}
)}
onClick={() => setCollapsed(false)}
>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={24} />
</button>
<Table
ref={tableRef}
columns={[
{ name: 'party', displayName: t('Trader') },
{ name: 'joined', displayName: t('Date Joined') },
{ name: 'volume', displayName: t('Volume (last 30 days)') },
{
name: 'commission',
displayName: t('Commission earned (last 30 days)'),
},
]}
data={sortBy(
data.referees.map((r) => ({
party: (
<span title={r.refereeId}>
{truncateMiddle(r.refereeId)}
</span>
),
joined: getDateTimeFormat().format(new Date(r.joinedAt)),
volume: Number(r.totalRefereeNotionalTakerVolume),
commission: Number(r.totalRefereeGeneratedRewards),
})),
(r) => r.volume
)
.map((r) => ({
...r,
volume: getNumberFormat(0).format(r.volume),
commission: getNumberFormat(0).format(r.commission),
}))
.reverse()}
/>
</div>
</div>
)}
</>
</div>
);
};
@@ -1,5 +1,4 @@
import {
Loader,
TradingAnchorButton,
VegaIcon,
VegaIconNames,
@@ -7,99 +6,51 @@ import {
import { HowItWorksTable } from './how-it-works-table';
import { LandingBanner } from './landing-banner';
import { TiersContainer } from './tiers';
import { TabLink } from './buttons';
import { RainbowTabLink } from './buttons';
import { Outlet } from 'react-router-dom';
import { Routes } from '../../lib/links';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
import { REFERRAL_DOCS_LINK } from './constants';
import classNames from 'classnames';
import { usePageTitleStore } from '../../stores';
import { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
const Nav = () => (
<div className="flex justify-center border-b border-vega-cdark-500">
<TabLink end to={Routes.REFERRALS}>
{t('I want a code')}
</TabLink>
<TabLink to={Routes.REFERRALS_APPLY_CODE}>{t('I have a code')}</TabLink>
</div>
);
export const Referrals = () => {
const { pubKey } = useVegaWallet();
const {
data: referee,
loading: refereeLoading,
error: refereeError,
} = useReferral({
pubKey,
role: 'referee',
});
const {
data: referrer,
loading: referrerLoading,
error: referrerError,
} = useReferral({
pubKey,
role: 'referrer',
});
const error = refereeError || referrerError;
const loading = refereeLoading || referrerLoading;
const showNav = !loading && !error && !referrer && !referee;
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
useEffect(() => {
updateTitle(titlefy([t('Referrals')]));
}, [updateTitle]);
const { data: referee } = useReferral(pubKey, 'referee');
const { data: referrer } = useReferral(pubKey, 'referrer');
return (
<>
<LandingBanner />
{showNav && <Nav />}
<div
className={classNames({
'py-16': showNav,
'h-[300px] relative': loading || error,
})}
>
{loading ? (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<Loader />
</div>
) : error ? (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
<p>{t('Something went wrong')}</p>
<span className="text-xs">{error.message}</span>
</div>
) : (
<div>
<div className="flex justify-center">
<RainbowTabLink end to={Routes.REFERRALS}>
Your referrals
</RainbowTabLink>
<RainbowTabLink
disabled={Boolean(referee || referrer)}
to={Routes.REFERRALS_APPLY_CODE}
>
Apply a code
</RainbowTabLink>
</div>
<div className="py-16 border-t border-b border-vega-cdark-500">
<Outlet />
)}
</div>
</div>
<TiersContainer />
<div className="mt-10 mb-5 text-center">
<h2 className="text-2xl">{t('How it works')}</h2>
<h2 className="text-2xl">How it works</h2>
</div>
<div className="md:w-[60%] mx-auto">
<HowItWorksTable />
<div className="mt-5">
<TradingAnchorButton
className="mx-auto w-max"
href={REFERRAL_DOCS_LINK}
href="https://docs.vega.xyz/"
target="_blank"
>
{t('Read the terms')}{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
Read the terms <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</TradingAnchorButton>
</div>
</div>
+95 -105
View File
@@ -1,6 +1,6 @@
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import { forwardRef, type HTMLAttributes } from 'react';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TableColumnDefinition = {
@@ -19,108 +19,98 @@ type TableProps = {
const INNER_BORDER_STYLE = `border-b ${BORDER_COLOR}`;
export const Table = forwardRef<
HTMLTableElement,
TableProps & HTMLAttributes<HTMLTableElement>
>(
(
{
columns,
data,
noHeader = false,
noCollapse = false,
className,
...props
},
ref
) => {
const header = (
<thead className={classNames({ 'max-md:hidden': !noCollapse })}>
<tr>
{columns.map(({ displayName, name, tooltip }) => (
<th
key={name}
col-id={name}
className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100 font-normal',
INNER_BORDER_STYLE
)}
>
<span className="flex flex-row gap-2 items-center">
<span>{displayName}</span>
{tooltip ? (
<Tooltip description={tooltip}>
<button className="text-vega-clight-400 dark:text-vega-cdark-400 no-underline decoration-transparent w-[12px] h-[12px] inline-flex">
<VegaIcon size={12} name={VegaIconNames.INFO} />
</button>
</Tooltip>
) : null}
</span>
</th>
))}
</tr>
</thead>
);
return (
<table
ref={ref}
className={classNames(
'w-full',
'border-separate border rounded-md border-spacing-0',
BORDER_COLOR,
GRADIENT,
className
)}
{...props}
>
{!noHeader && header}
<tbody>
{data.map((d, i) => (
<tr
key={i}
className={classNames(d['className'] as string, {
'max-md:flex flex-col w-full': !noCollapse,
})}
>
{columns.map(({ name, displayName, className }, j) => (
<td
className={classNames(
'px-5 py-3 text-base',
{
'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse,
},
INNER_BORDER_STYLE,
{
'border-none': i === data.length - 1 && noCollapse,
'md:border-none': i === data.length - 1,
'max-md:border-none':
i === data.length - 1 && j === columns.length - 1,
},
className
export const Table = ({
columns,
data,
noHeader = false,
noCollapse = false,
className,
...props
}: TableProps & HTMLAttributes<HTMLTableElement>) => {
const header = (
<thead className={classNames({ 'max-md:hidden': !noCollapse })}>
<tr>
{columns.map(({ displayName, name, tooltip }) => (
<th
key={name}
col-id={name}
className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100',
INNER_BORDER_STYLE
)}
>
<span className="flex flex-row gap-2 items-center">
<span>{displayName}</span>
{tooltip ? (
<Tooltip description={tooltip}>
<button className="text-vega-clight-400 dark:text-vega-cdark-400 no-underline decoration-transparent w-[12px] h-[12px] inline-flex">
<VegaIcon size={12} name={VegaIconNames.INFO} />
</button>
</Tooltip>
) : null}
</span>
</th>
))}
</tr>
</thead>
);
return (
<table
className={classNames(
'w-full',
'border-separate border rounded-md border-spacing-0',
BORDER_COLOR,
GRADIENT,
className
)}
{...props}
>
{!noHeader && header}
<tbody>
{data.map((d, i) => (
<tr
key={i}
className={classNames(d['className'] as string, {
'max-md:flex flex-col w-full': !noCollapse,
})}
>
{columns.map(({ name, displayName, className }, j) => (
<td
className={classNames(
'px-5 py-3 text-base',
{
'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse,
},
INNER_BORDER_STYLE,
{
'border-none': i === data.length - 1 && noCollapse,
'md:border-none': i === data.length - 1,
'max-md:border-none':
i === data.length - 1 && j === columns.length - 1,
},
className
)}
key={`${i}-${name}`}
>
{/** display column name in mobile view */}
{!noCollapse &&
!noHeader &&
displayName &&
displayName.length > 0 && (
<span
aria-hidden
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
>
{displayName}
</span>
)}
key={`${i}-${name}`}
>
{/** display column name in mobile view */}
{!noCollapse &&
!noHeader &&
displayName &&
displayName.length > 0 && (
<span
aria-hidden
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
>
{displayName}
</span>
)}
<span>{d[name]}</span>
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
);
Table.displayName = 'Table';
<span>{d[name]}</span>
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
+1 -1
View File
@@ -12,7 +12,7 @@ export const Tag = ({
}: TagProps & HTMLAttributes<HTMLDivElement>) => (
<div
className={classNames(
'w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
'mt-3 w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
{
'border-vega-yellow-500 text-vega-yellow-500': color === 'yellow',
'border-vega-green-500 text-vega-green-500': color === 'green',
+28 -58
View File
@@ -4,10 +4,7 @@ import { Table } from './table';
import classNames from 'classnames';
import { BORDER_COLOR, GRADIENT } from './constants';
import { Tag } from './tag';
import type { ComponentProps, ReactNode } from 'react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { DApp, TOKEN_PROPOSALS, useLinks } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import type { ComponentProps } from 'react';
const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
<div
@@ -41,66 +38,51 @@ const StakingTier = ({
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
'flex flex-row',
GRADIENT,
BORDER_COLOR
)}
>
<div aria-hidden className="max-w-[120px]">
<div aria-hidden>
{tier < 4 && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={`/${tier}x.png`}
alt={`${referralRewardMultiplier}x multiplier`}
width={240}
height={240}
className="w-full h-full"
width={768}
height={400}
className="w-full"
/>
)}
</div>
<div className={classNames('p-3')}>
<Tag color={color[tier]}>Multiplier {referralRewardMultiplier}x</Tag>
<h3 className="mt-1 mb-1 text-base">{label}</h3>
<p className="text-sm text-vega-clight-100 dark:text-vega-cdark-100">
{t('Stake a minimum of')} {minimumStakedTokens} {t('$VEGA tokens')}
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">{label}</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of {minimumStakedTokens} $VEGA tokens
</p>
<Tag color={color[tier]}>
Reward multiplier {referralRewardMultiplier}x
</Tag>
</div>
</div>
);
};
export const TiersContainer = () => {
const { benefitTiers, stakingTiers, details, loading, error } =
useReferralProgram();
const { benefitTiers, stakingTiers, details, loading } = useReferralProgram();
const ends = details?.endOfProgramTimestamp
? getDateTimeFormat().format(new Date(details.endOfProgramTimestamp))
: undefined;
const governanceLink = useLinks(DApp.Governance);
if ((!loading && !details) || error) {
return (
<div className="text-base px-5 py-10 text-center">
{t(
"We're sorry but we don't have an active referral programme currently running. You can propose a new programme"
)}{' '}
<ExternalLink href={governanceLink(TOKEN_PROPOSALS)}>
{t('here')}
</ExternalLink>
.
</div>
);
}
return (
<>
{/* Benefit tiers */}
<div className="flex flex-col items-baseline justify-between mt-10 mb-5">
<h2 className="text-2xl">{t('Referral tiers')}</h2>
<div className="flex flex-row items-baseline justify-between mt-10 mb-5">
<h2 className="text-2xl">Referral tiers</h2>
{ends && (
<span className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
{t('Program ends:')} {ends}
<span className="text-base">
<span className="text-vega-clight-200 dark:text-vega-cdark-200">
Program ends:
</span>{' '}
{ends}
</span>
)}
</div>
@@ -108,24 +90,14 @@ export const TiersContainer = () => {
{loading || !benefitTiers || benefitTiers.length === 0 ? (
<Loading variant="large" />
) : (
<TiersTable
data={benefitTiers.map((bt) => ({
...bt,
tierElement: (
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
{bt.tier}
</div>
),
}))}
/>
<TiersTable data={benefitTiers} />
)}
</div>
{/* Staking tiers */}
<div className="flex flex-row items-baseline justify-between mb-5">
<h2 className="text-2xl">{t('Staking multipliers')}</h2>
<h2 className="text-2xl">Staking multipliers</h2>
</div>
<div className="mb-20 flex flex-col justify-items-stretch lg:flex-row gap-5">
<div className="flex flex-col mb-20 justify-items-stretch md:flex-row gap-5">
{loading || !stakingTiers || stakingTiers.length === 0 ? (
<>
<Loading variant="large" />
@@ -165,7 +137,6 @@ const TiersTable = ({
}: {
data: Array<{
tier: number;
tierElement: ReactNode;
commission: string;
discount: string;
volume: string;
@@ -174,15 +145,14 @@ const TiersTable = ({
return (
<Table
columns={[
{ name: 'tierElement', displayName: t('Tier') },
{ name: 'tier', displayName: 'Tier' },
{
name: 'commission',
displayName: t('Referrer commission'),
tooltip: t('A percentage of commission earned by the referrer'),
displayName: 'Referrer commission',
tooltip: 'A percentage of commission earned by the referrer',
},
{ name: 'discount', displayName: t('Referrer trading discount') },
{ name: 'volume', displayName: t('Min. trading volume') },
{ name: 'epochs', displayName: t('Min. epochs') },
{ name: 'discount', displayName: 'Referrer trading discount' },
{ name: 'volume', displayName: 'Min. trading volume' },
]}
data={data.map((d) => ({
...d,
+26 -79
View File
@@ -1,92 +1,39 @@
import {
CopyWithTooltip,
Tooltip,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import { Button } from './buttons';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TileProps = {
variant?: 'rainbow' | 'default';
};
export const Tile = ({
variant = 'default',
className,
children,
}: HTMLAttributes<HTMLDivElement>) => {
}: TileProps & HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={classNames(
'rounded-lg overflow-hidden relative',
'bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white',
'p-6',
className
{
'bg-rainbow p-[0.125rem]': variant === 'rainbow',
[`border-2 ${BORDER_COLOR} p-0`]: variant === 'default',
},
'rounded-lg overflow-hidden relative'
)}
>
{children}
<div
className={classNames(
{
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white rounded-[0.35rem] overflow-hidden':
variant === 'rainbow',
},
'p-6',
GRADIENT,
className
)}
>
{children}
</div>
</div>
);
};
type StatTileProps = {
title: string;
description?: string;
children?: ReactNode;
};
export const StatTile = ({ title, description, children }: StatTileProps) => {
return (
<Tile>
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
{title}
</h3>
<div className="text-5xl text-left">{children}</div>
{description && (
<div className="text-sm text-left text-vega-clight-100 dark:text-vega-cdark-100">
{description}
</div>
)}
</Tile>
);
};
const FADE_OUT_STYLE = classNames(
'after:w-5 after:h-full after:absolute after:top-0 after:right-0',
'after:bg-gradient-to-l after:from-vega-clight-800 after:dark:from-vega-cdark-800 after:to-transparent'
);
export const CodeTile = ({
code,
className,
}: {
code: string;
className?: string;
}) => {
return (
<StatTile title="Your referral code">
<div className="flex gap-2 items-center justify-between">
<Tooltip
description={
<div className="break-all">
<span className="text-xl bg-rainbow bg-clip-text text-transparent">
{code}
</span>
</div>
}
>
<div
className={classNames(
'relative bg-rainbow bg-clip-text text-transparent text-5xl overflow-hidden',
FADE_OUT_STYLE
)}
>
{code}
</div>
</Tooltip>
<CopyWithTooltip text={code}>
<Button className="text-sm no-underline !py-0 !px-0 h-fit !bg-transparent">
<span className="sr-only">Copy</span>
<VegaIcon size={24} name={VegaIconNames.COPY} />
</Button>
</CopyWithTooltip>
</div>
</StatTile>
);
};
@@ -9,36 +9,10 @@ import { useMemo } from 'react';
import { t } from '@vegaprotocol/i18n';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { Splash } from '@vegaprotocol/ui-toolkit';
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;
}
};
const DateRange = {
RANGE_1D: '1D',
RANGE_7D: '7D',
RANGE_1M: '1M',
RANGE_3M: '3M',
RANGE_1Y: '1Y',
RANGE_YTD: 'YTD',
RANGE_ALL: 'All',
};
import {
DateRange,
calculateStartDate,
} from '../../client-pages/portfolio/account-history-container';
export const FundingContainer = ({ marketId }: { marketId: string }) => {
const { theme } = useThemeSwitcher();
@@ -18,7 +18,7 @@ import BigNumber from 'bignumber.js';
import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity';
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
import {
addDecimalsFormatNumberQuantum,
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
@@ -30,14 +30,12 @@ interface Props {
marketId?: string;
noUpdate?: boolean;
assetDecimals: number;
quantum: string;
}
export const MarketLiquiditySupplied = ({
marketId,
assetDecimals,
noUpdate = false,
quantum,
}: Props) => {
const [market, setMarket] = useState<MarketData>();
const { params } = useNetworkParams([
@@ -81,12 +79,11 @@ export const MarketLiquiditySupplied = ({
});
const supplied = market?.suppliedStake
? addDecimalsFormatNumberQuantum(
? addDecimalsFormatNumber(
new BigNumber(market?.suppliedStake)
.multipliedBy(stakeToCcyVolume || 1)
.toString(),
assetDecimals,
quantum
assetDecimals
)
: '-';
@@ -111,10 +108,9 @@ export const MarketLiquiditySupplied = ({
<span>{t('Supplied stake')}</span>
<span>
{market?.suppliedStake
? addDecimalsFormatNumberQuantum(
market?.suppliedStake,
assetDecimals,
quantum
? addDecimalsFormatNumber(
new BigNumber(market?.suppliedStake).toString(),
assetDecimals
)
: '-'}
</span>
@@ -123,10 +119,9 @@ export const MarketLiquiditySupplied = ({
<span>{t('Target stake')}</span>
<span>
{market?.targetStake
? addDecimalsFormatNumberQuantum(
market?.targetStake,
assetDecimals,
quantum
? addDecimalsFormatNumber(
new BigNumber(market?.targetStake).toString(),
assetDecimals
)
: '-'}
</span>
@@ -1,9 +1,6 @@
import { Fragment, useState } from 'react';
import {
marketViewProposalsDataProvider,
type NewMarketSuccessorFieldsFragment,
} from '@vegaprotocol/proposals';
import type { NewMarketSuccessorFieldsFragment } from '@vegaprotocol/proposals';
import { useMarketViewProposals } from '@vegaprotocol/proposals';
import {
ExternalLink,
Intent,
@@ -12,20 +9,17 @@ import {
import { t } from '@vegaprotocol/i18n';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import * as Types from '@vegaprotocol/types';
import { useDataProvider } from '@vegaprotocol/data-provider';
export const MarketSuccessorProposalBanner = ({
marketId,
}: {
marketId?: string;
}) => {
const { data: proposals } = useDataProvider({
dataProvider: marketViewProposalsDataProvider,
const proposals = useMarketViewProposals({
skip: !marketId,
variables: {
inState: Types.ProposalState.STATE_OPEN,
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
inState: Types.ProposalState.STATE_OPEN,
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
typename: 'NewMarket',
});
const successors =
@@ -20,13 +20,10 @@ const marketMock = {
},
} as Market;
const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
request: {
query: MarketViewProposalsDocument,
variables: {
inState: Types.ProposalState.STATE_PASSED,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
},
variables: { inState: Types.ProposalState.STATE_PASSED },
},
result: {
data: {
@@ -34,7 +31,7 @@ const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
edges: [
{
node: {
id: '1',
id: 'first-id',
state: Types.ProposalState.STATE_PASSED,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
@@ -59,7 +56,7 @@ const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
},
{
node: {
id: '2',
id: 'second-id',
state: Types.ProposalState.STATE_PASSED,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
@@ -87,99 +84,7 @@ const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
},
},
};
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];
const mocks: MockedResponse[] = [proposalMock];
describe('MarketTerminationBanner', () => {
beforeAll(() => {
@@ -203,28 +108,4 @@ describe('MarketTerminationBanner', () => {
screen.getByTestId('termination-warning-banner-market-1')
).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,67 +1,12 @@
import type { ReactNode } from 'react';
import { useState } from 'react';
import { format, formatDuration, intervalToDuration } from 'date-fns';
import {
ExternalLink,
Intent,
NotificationBanner,
} from '@vegaprotocol/ui-toolkit';
import type { MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
import { marketViewProposalsDataProvider } from '@vegaprotocol/proposals';
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
import { useMarketViewProposals } from '@vegaprotocol/proposals';
import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types';
import type { Market } from '@vegaprotocol/markets';
import { getQuoteName } from '@vegaprotocol/markets';
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 = ({
market,
@@ -70,55 +15,57 @@ export const MarketTerminationBanner = ({
}) => {
const [visible, setVisible] = useState(true);
const skip = !market || !visible;
const { data: passedProposalsData } = useDataProvider({
dataProvider: marketViewProposalsDataProvider,
const proposalsData = useMarketViewProposals({
skip,
variables: {
inState: Types.ProposalState.STATE_PASSED,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
},
inState: Types.ProposalState.STATE_PASSED,
typename: 'UpdateMarketState',
});
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;
const now = Date.now();
const passedProposals = filterProposals(passedProposalsData, market.id, now);
const openProposals = filterProposals(openProposalsData, market.id, now);
const marketFound = (proposalsData || []).find(
(item) =>
item.terms.change.__typename === 'UpdateMarketState' &&
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 name = market.tradableInstrument.instrument.code;
if (!passedProposals.length && !openProposals.length) {
return null;
}
const enactmentDatetime = new Date(marketFound?.terms.enactmentDatetime);
const name =
marketFound?.terms.change.__typename === 'UpdateMarketState'
? marketFound.terms.change.market.tradableInstrument.instrument.code
: '';
const assetSymbol = getQuoteName(market);
const proposalLink =
!passedProposals.length && openProposals[0]?.id
? governanceLink(TOKEN_PROPOSAL.replace(':id', openProposals[0]?.id))
: undefined;
const proposalsLink =
openProposals.length > 1 ? governanceLink(TOKEN_PROPOSALS) : undefined;
let content: ReactNode;
if (passedProposals.length) {
const { date, duration, price } = getMessageVariables(passedProposals[0]);
content = (
<>
if (name && enactmentDatetime.getTime() > Date.now()) {
const dayMonthDate = format(enactmentDatetime, 'dd MMMM');
const duration = intervalToDuration({
start: new Date(),
end: enactmentDatetime,
});
const formattedDuration = formatDuration(duration, {
format: ['days', 'hours'],
});
const price =
marketFound?.terms.change.__typename === 'UpdateMarketState'
? 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">
{t('Trading on Market %s will stop on %s', [name, date])}
{t('Trading on Market %s will stop on %s', [name, dayMonthDate])}
</div>
<div>
{t(
'You will no longer be able to hold a position on this market when it closes in %s.',
[duration]
[formattedDuration]
)}{' '}
{price &&
assetSymbol &&
@@ -127,57 +74,8 @@ export const MarketTerminationBanner = ({
assetSymbol,
])}
</div>
</>
);
} 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>
</>
</NotificationBanner>
);
}
return (
<NotificationBanner
intent={openProposals.length ? Intent.Warning : Intent.Info}
onClose={() => {
setVisible(false);
}}
data-testid={`termination-warning-banner-${market.id}`}
>
{content}
</NotificationBanner>
);
return null;
};
+2 -4
View File
@@ -182,7 +182,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
</NavbarItem>
{FLAGS.REFERRALS && (
<NavbarItem>
<NavbarLink end={false} to={Links.REFERRALS()} onClick={onClick}>
<NavbarLink to={Links.REFERRALS()} onClick={onClick}>
{t('Referrals')}
</NavbarLink>
</NavbarItem>
@@ -255,18 +255,16 @@ const NavbarLink = ({
children,
to,
onClick,
end = true,
}: {
children: ReactNode;
to: string;
onClick?: () => void;
end?: boolean;
}) => {
return (
<N.Link asChild={true}>
<NavLink
to={to}
end={end}
end={true}
className={classNames(
'block lg:flex lg:h-full flex-col justify-center',
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
@@ -10,19 +10,6 @@ import type { DataGridStore } from '../../stores/datagrid-store-slice';
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 = {
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
[Filter.Closed]: [
@@ -56,7 +43,6 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
const noRowsMessage = resolveNoRowsMessage(filter);
return (
<OrderListManager
@@ -70,7 +56,6 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
}
isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks}
noRowsMessage={noRowsMessage}
/>
);
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 574 KiB

+2 -136
View File
@@ -24,8 +24,8 @@ module.exports = {
...theme.backgroundImage,
rainbow:
'linear-gradient(103.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
'rainbow-180':
'linear-gradient(283.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
'rainbow-shifted':
'linear-gradient(103.47deg, #0075FF 1.68%, #8028FF 47.49%, #FF077F 100%)',
highlight:
'linear-gradient(170deg, var(--tw-gradient-from), transparent var(--tw-gradient-to-position))',
},
@@ -38,144 +38,10 @@ module.exports = {
'75%': { transform: 'translateX(5px)' },
'100%': { transform: 'translateX(0)' },
},
'spin-rainbow-180': {
'0%': {
backgroundImage:
'linear-gradient(103.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'10%': {
backgroundImage:
'linear-gradient(121.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'20%': {
backgroundImage:
'linear-gradient(139.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'30%': {
backgroundImage:
'linear-gradient(157.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'40%': {
backgroundImage:
'linear-gradient(175.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'50%': {
backgroundImage:
'linear-gradient(193.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'60%': {
backgroundImage:
'linear-gradient(211.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'70%': {
backgroundImage:
'linear-gradient(229.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'80%': {
backgroundImage:
'linear-gradient(247.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'90%': {
backgroundImage:
'linear-gradient(265.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'100%': {
backgroundImage:
'linear-gradient(283.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
},
'spin-rainbow-360': {
'0%': {
backgroundImage:
'linear-gradient(103.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'5%': {
backgroundImage:
'linear-gradient(121.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'10%': {
backgroundImage:
'linear-gradient(139.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'15%': {
backgroundImage:
'linear-gradient(157.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'20%': {
backgroundImage:
'linear-gradient(175.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'25%': {
backgroundImage:
'linear-gradient(193.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'30%': {
backgroundImage:
'linear-gradient(211.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'35%': {
backgroundImage:
'linear-gradient(229.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'40%': {
backgroundImage:
'linear-gradient(247.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'45%': {
backgroundImage:
'linear-gradient(265.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'50%': {
backgroundImage:
'linear-gradient(283.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'55%': {
backgroundImage:
'linear-gradient(301.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'60%': {
backgroundImage:
'linear-gradient(319.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'65%': {
backgroundImage:
'linear-gradient(337.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'70%': {
backgroundImage:
'linear-gradient(355.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'75%': {
backgroundImage:
'linear-gradient(13.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'80%': {
backgroundImage:
'linear-gradient(31.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'85%': {
backgroundImage:
'linear-gradient(49.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'90%': {
backgroundImage:
'linear-gradient(67.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'95%': {
backgroundImage:
'linear-gradient(85.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
'100%': {
backgroundImage:
'linear-gradient(103.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
},
},
},
animation: {
...theme.animation,
shake: 'shake 200ms linear',
'spin-rainbow': 'spin-rainbow-180 500ms linear',
'rotate-rainbow': 'spin-rainbow-360 1000ms linear',
},
},
},
+1 -3
View File
@@ -63,9 +63,7 @@ export const CandlesChartContainer = ({
overlays,
studies,
notEnoughDataText: (
<span className="text-xs text-center">
{t('No open orders')}
</span>
<span className="text-xs text-center">{t('No data')}</span>
),
initialNumCandlesToDisplay: candlesCount,
studySize: STUDY_SIZE,
@@ -43,7 +43,7 @@ import {
REDUCE_ONLY_TOOLTIP,
stopSubmit,
getNotionalSize,
getBaseQuoteUnit,
getAssetUnit,
} from './deal-ticket';
import { TypeToggle } from './type-selector';
import {
@@ -854,7 +854,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}, [watch, market.id, updateStoredFormValues]);
const quoteName = getQuoteName(market);
const assetUnit = getBaseQuoteUnit(
const assetUnit = getAssetUnit(
market.tradableInstrument.instrument.metadata.tags
);
@@ -127,7 +127,7 @@ const getDefaultValues = (
...storedValues,
});
export const getBaseQuoteUnit = (tags?: string[] | null) =>
export const getAssetUnit = (tags?: string[] | null) =>
tags
?.find((tag) => tag.startsWith('base:') || tag.startsWith('ticker:'))
?.replace(/^[^:]*:/, '');
@@ -273,7 +273,7 @@ export const DealTicket = ({
const assetSymbol = getAsset(market).symbol;
const baseQuote = getBaseQuoteUnit(
const assetUnit = getAssetUnit(
market.tradableInstrument.instrument.metadata.tags
);
@@ -414,7 +414,7 @@ export const DealTicket = ({
id="order-size"
className="w-full"
type="number"
appendElement={baseQuote && <Pill size="xs">{baseQuote}</Pill>}
appendElement={assetUnit && <Pill size="xs">{assetUnit}</Pill>}
step={sizeStep}
min={sizeStep}
data-testid="order-size"
@@ -670,7 +670,7 @@ export const DealTicket = ({
subLabel={`${formatValue(
normalizedOrder.size,
market.positionDecimalPlaces
)} ${baseQuote} @ ${
)} ${assetUnit} @ ${
type === Schema.OrderType.TYPE_MARKET
? 'market'
: `${formatValue(
+1 -64
View File
@@ -1,10 +1,7 @@
import merge from 'lodash/merge';
import * as Schema from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import type {
LiquidityProvidersQuery,
LiquidityProvisionsQuery,
} from './__generated__/MarketLiquidity';
import type { LiquidityProvisionsQuery } from './__generated__/MarketLiquidity';
import type { LiquidityProvisionFieldsFragment } from './__generated__/MarketLiquidity';
export const liquidityProvisionsQuery = (
@@ -26,66 +23,6 @@ export const liquidityProvisionsQuery = (
return merge(defaultResult, override);
};
export const liquidityProvidersQuery = (
override?: PartialDeep<LiquidityProvidersQuery>
): LiquidityProvidersQuery => {
const defaultResult: LiquidityProvidersQuery = {
liquidityProviders: {
edges: [
{
node: {
partyId:
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
marketId:
'5ddb6f1570c0ef7aea41ebfef234dbded4ce2c11722cf033954459c45c30c057',
feeShare: {
equityLikeShare: '1',
averageEntryValuation: '3570452966575.2571864668476351',
averageScore: '0',
virtualStake: '296386536856.9999884883855020',
},
sla: {
currentEpochFractionOfTimeOnBook: '0',
lastEpochFractionOfTimeOnBook: '0',
lastEpochFeePenalty: '1',
lastEpochBondPenalty: '0.05',
hysteresisPeriodFeePenalties: ['1'],
requiredLiquidity: '',
notionalVolumeBuys: '',
notionalVolumeSells: '',
},
},
},
{
node: {
partyId:
'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
marketId:
'5ddb6f1570c0ef7aea41ebfef234dbded4ce2c11722cf033954459c45c30c057',
feeShare: {
equityLikeShare: '1',
averageEntryValuation: '3570452966575.2571864668476351',
averageScore: '0',
virtualStake: '296386536856.9999884883855020',
},
sla: {
currentEpochFractionOfTimeOnBook: '0',
lastEpochFractionOfTimeOnBook: '0',
lastEpochFeePenalty: '1',
lastEpochBondPenalty: '0.05',
hysteresisPeriodFeePenalties: ['1'],
requiredLiquidity: '',
notionalVolumeBuys: '',
notionalVolumeSells: '',
},
},
},
],
},
};
return merge(defaultResult, override);
};
export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
{
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
+1 -1
View File
@@ -238,7 +238,7 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
volumeFormat={volumeFormat}
priceFormat={priceFormat}
notEnoughDataText={
<span className="text-xs text-center">{t('No open orders')}</span>
<span className="text-xs text-center">{t('No data')}</span>
}
/>
)}
@@ -11,8 +11,14 @@ import type {
MarketDepthQuery,
MarketDepthQueryVariables,
MarketDepthUpdateSubscription,
PriceLevelFieldsFragment,
} from './__generated__/MarketDepth';
export type OrderbookData = {
asks: PriceLevelFieldsFragment[];
bids: PriceLevelFieldsFragment[];
};
interface OrderbookManagerProps {
marketId: string;
onClick: (args: { price?: string; size?: string }) => void;
+1 -1
View File
@@ -238,7 +238,7 @@ export const Orderbook = ({
</>
) : (
<div className="absolute inset-0">
<Splash>{t('No open orders')}</Splash>
<Splash>{t('No data')}</Splash>
</div>
)}
</div>
@@ -4,10 +4,10 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaTransactionStore } from '@vegaprotocol/web3';
import { useHasAmendableOrder } from '../../order-hooks';
export const OpenOrdersMenu = () => {
export const OpenOrdersMenu = ({ marketId }: { marketId: string }) => {
const { isReadOnly } = useVegaWallet();
const create = useVegaTransactionStore((state) => state.create);
const hasAmendableOrder = useHasAmendableOrder();
const hasAmendableOrder = useHasAmendableOrder(marketId);
if (isReadOnly) {
return null;
@@ -239,11 +239,11 @@ export const hasActiveOrderProvider = makeDerivedDataProvider<
export const hasAmendableOrderProvider = makeDerivedDataProvider<
boolean,
never,
{ partyId: string }
{ partyId: string; marketId?: string }
>([activeOrdersProvider], (parts) => {
const activeOrders = parts[0] as ReturnType<typeof getData>;
const hasAmendableOrder = activeOrders.some(
(order) => !order.liquidityProvision
(order) => !(order.liquidityProvision || order.peggedOrder)
);
return hasAmendableOrder;
});
@@ -1,18 +1,23 @@
import { render, screen } from '@testing-library/react';
import type { OrderListManagerProps } from './order-list-manager';
import { OrderListManager, Filter } from './order-list-manager';
import { render, screen, act } from '@testing-library/react';
import { OrderListManager } from './order-list-manager';
import * as useDataProviderHook from '@vegaprotocol/data-provider';
import type { OrderFieldsFragment } from '../';
import * as orderListMock from '../order-list/order-list';
import { forwardRef } from 'react';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
const generateJsx = (props: Partial<OrderListManagerProps> | null = null) => {
// @ts-ignore OrderList is read only but we need to override with the forwardRef to
// avoid warnings about padding refs
orderListMock.OrderListTable = forwardRef(() => <div>OrderList</div>);
const generateJsx = () => {
const pubKey = '0x123';
return (
<MockedProvider>
<VegaWalletContext.Provider value={{ pubKey } as VegaWalletContextShape}>
<OrderListManager partyId={pubKey} isReadOnly={false} {...props} />
<OrderListManager partyId={pubKey} isReadOnly={false} />
</VegaWalletContext.Provider>
</MockedProvider>
);
@@ -28,46 +33,9 @@ describe('OrderListManager', () => {
reload: jest.fn(),
load: jest.fn(),
});
render(generateJsx());
expect(
await screen.getByRole('treegrid', {
name: (_, element) => element.classList.contains('ag-root'),
})
).toBeInTheDocument();
});
it('should display order info', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [{ id: '1' } as OrderFieldsFragment],
loading: false,
error: new Error('Query error'),
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
await act(async () => {
render(generateJsx());
});
render(generateJsx());
expect(
await screen.getByText('Something went wrong: Query error')
).toBeInTheDocument();
});
it('should display no rows overlay', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [],
loading: false,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
});
render(
generateJsx({ filter: Filter.Closed, noRowsMessage: 'no rows message' })
);
expect(screen.getByText('no rows message')).toBeInTheDocument();
expect(await screen.findByText('OrderList')).toBeInTheDocument();
});
});
@@ -1,8 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback, useRef, useState, useEffect } from 'react';
import { useCallback, useRef, useState } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { FilterChangedEvent } from 'ag-grid-community';
import { OrderListTable } from '../order-list';
import { OrderListTable } from '../order-list/order-list';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
@@ -12,7 +11,6 @@ import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/web3';
import { OrderEditDialog } from '../order-list/order-edit-dialog';
import type { Order } from '../order-data-provider';
import { OrderViewDialog } from '../order-list/order-view-dialog';
import { Splash } from '@vegaprotocol/ui-toolkit';
export enum Filter {
'Open' = 'Open',
@@ -27,7 +25,6 @@ export interface OrderListManagerProps {
isReadOnly: boolean;
filter?: Filter;
gridProps?: ReturnType<typeof useDataGridEvents>;
noRowsMessage?: string;
}
export const OrderListManager = ({
@@ -37,7 +34,6 @@ export const OrderListManager = ({
isReadOnly,
filter,
gridProps,
noRowsMessage,
}: OrderListManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [editOrder, setEditOrder] = useState<Order | null>(null);
@@ -60,29 +56,6 @@ export const OrderListManager = ({
},
});
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
gridProps?.onFilterChanged?.(event);
if (event.api) {
const isEmpty = event.api.getDisplayedRowCount() === 0;
if (isEmpty) {
event.api.showNoRowsOverlay();
} else {
event.api.hideOverlay();
}
}
},
[gridProps]
);
useEffect(() => {
if (!data || !data.length) {
gridRef.current?.api?.showNoRowsOverlay();
} else {
gridRef.current?.api?.hideOverlay();
}
}, [data]);
const cancel = useCallback(
(order: Order) => {
if (!order.market) return;
@@ -96,10 +69,6 @@ export const OrderListManager = ({
[create]
);
if (error) {
return <Splash>{t(`Something went wrong: ${error.message}`)}</Splash>;
}
return (
<>
<div className="h-full relative">
@@ -113,9 +82,8 @@ export const OrderListManager = ({
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
isReadOnly={isReadOnly}
overlayNoRowsTemplate={noRowsMessage || t('No orders')}
overlayNoRowsTemplate={error ? error.message : t('No orders')}
{...gridProps}
onFilterChanged={onFilterChanged}
/>
</div>
{editOrder && (
@@ -1,32 +0,0 @@
import { act, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { OrderEditDialog } from './order-edit-dialog';
import { limitOrder } from '../mocks';
describe('OrderEditDialog', () => {
it('must be warned (pre-submit) if the input price has too many digits after the decimal place for the market', async () => {
// 7003-MORD-013
await act(async () => {
render(
<OrderEditDialog
order={limitOrder}
onChange={jest.fn()}
isOpen={true}
onSubmit={jest.fn()}
/>
);
});
const editOrder = await screen.findByTestId('edit-order');
const limitPrice = within(editOrder).getByLabelText('Price');
await userEvent.type(limitPrice, '0.111111');
const submitButton = within(editOrder).getByRole('button', {
name: 'Update',
});
await userEvent.click(submitButton);
const inputErrorText = within(editOrder).getByTestId('input-error-text');
expect(inputErrorText).toHaveTextContent(
'Price accepts up to 1 decimal places'
);
});
});
@@ -46,12 +46,6 @@ const generateJsx = (
describe('OrderListTable', () => {
it('should render correct columns', async () => {
// 7003-MORD-001
// 7003-MORD-002
// 7003-MORD-003
// 7003-MORD-004
// 7003-MORD-005
// 7003-MORD-006
await act(async () => {
render(generateJsx({ rowData: [marketOrder, limitOrder] }));
});
@@ -190,7 +184,6 @@ describe('OrderListTable', () => {
describe('amend cell', () => {
it('allows cancelling and editing for permitted orders', async () => {
// 7003-MORD-007
const mockEdit = jest.fn();
const mockCancel = jest.fn();
const order = generateOrder({
@@ -216,7 +209,6 @@ describe('OrderListTable', () => {
});
it('does not allow cancelling and editing for permitted orders if read only', async () => {
// 7003-MORD-008
const mockEdit = jest.fn();
const mockCancel = jest.fn();
const order = generateOrder({
@@ -241,7 +233,6 @@ describe('OrderListTable', () => {
});
it('shows if an order is a liquidity provision order and does not show order actions', async () => {
// 7003-MORD-008
const order = generateOrder({
type: Schema.OrderType.TYPE_LIMIT,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
@@ -260,7 +251,6 @@ describe('OrderListTable', () => {
});
it('shows if an order is a pegged order and does not show order actions', async () => {
// 7003-MORD-008
const order = generateOrder({
type: Schema.OrderType.TYPE_LIMIT,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
@@ -34,7 +34,7 @@ import type {
} from '@vegaprotocol/datagrid';
import type { AgGridReact } from 'ag-grid-react';
import type { Order } from '../order-data-provider';
import { Filter } from '../order-list-manager/order-list-manager';
import { Filter } from '../order-list-manager';
import type { ColDef } from 'ag-grid-community';
const defaultColDef = {
@@ -3,7 +3,7 @@ import { hasAmendableOrderProvider } from '../components/order-data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/data-provider';
export const useHasAmendableOrder = () => {
export const useHasAmendableOrder = (marketId?: string) => {
const { pubKey } = useVegaWallet();
const [hasAmendableOrder, setHasAmendableOrder] = useState(false);
const update = useCallback(({ data }: { data: boolean | null }) => {
@@ -15,6 +15,7 @@ export const useHasAmendableOrder = () => {
update,
variables: {
partyId: pubKey || '',
marketId,
},
skip: !pubKey,
});
@@ -1,5 +1,2 @@
export {
marketViewProposalsDataProvider,
proposalsDataProvider,
} from './proposals-data-provider';
export * from './proposals-data-provider';
export * from './__generated__/Proposals';
@@ -1,67 +0,0 @@
import { update } from './proposals-data-provider';
import type {
MarketViewProposalFieldsFragment,
MarketViewProposalsQueryVariables,
} from './__generated__/Proposals';
import * as Types from '@vegaprotocol/types';
describe('proposals data provider', () => {
const proposal = {
id: '1',
state: Types.ProposalState.STATE_OPEN,
terms: {
change: {
__typename: 'UpdateMarketState',
},
},
} as MarketViewProposalFieldsFragment;
const data = [proposal];
const delta = { ...proposal };
const reload = jest.fn();
const variables: MarketViewProposalsQueryVariables = {
inState: Types.ProposalState.STATE_OPEN,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
};
it('update existing data', () => {
expect(update(data, delta, reload, variables)?.[0]).toBe(delta);
});
it('removes existing data if delta do not match inState', () => {
expect(
update(
data,
{ ...delta, state: Types.ProposalState.STATE_PASSED },
reload,
variables
)?.length
).toBe(0);
});
it('do not add delta if it do not match inState', () => {
expect(
update(
data,
{ ...delta, id: '2', state: Types.ProposalState.STATE_PASSED },
reload,
variables
)?.length
).toBe(1);
});
it('do not add delta if it do not match proposalType', () => {
expect(
update(
data,
{
...delta,
id: '2',
terms: { ...delta.terms, change: { __typename: 'UpdateMarket' } },
},
reload,
variables
)?.length
).toBe(1);
});
});
@@ -1,7 +1,5 @@
import type { Update } from '@vegaprotocol/data-provider';
import { makeDataProvider } from '@vegaprotocol/data-provider';
import produce from 'immer';
import * as Types from '@vegaprotocol/types';
import type {
ProposalsListQuery,
ProposalsListQueryVariables,
@@ -10,7 +8,6 @@ import type {
MarketViewProposalFieldsFragment,
MarketViewProposalsQuery,
MarketViewProposalsQueryVariables,
MarketViewLiveProposalsSubscriptionVariables,
} from './__generated__/Proposals';
import {
MarketViewLiveProposalsDocument,
@@ -45,61 +42,21 @@ export const proposalsDataProvider = makeDataProvider<
errors.every((e) => e.message.match(/failed to get asset for ID/)),
});
const ProposalTypeMap: Record<
Types.ProposalType,
Types.ProposalChange['__typename']
> = {
[Types.ProposalType.TYPE_CANCEL_TRANSFER]: 'CancelTransfer',
[Types.ProposalType.TYPE_NETWORK_PARAMETERS]: 'UpdateNetworkParameter',
[Types.ProposalType.TYPE_NEW_ASSET]: 'NewAsset',
[Types.ProposalType.TYPE_NEW_FREE_FORM]: 'NewFreeform',
[Types.ProposalType.TYPE_NEW_MARKET]: 'NewMarket',
[Types.ProposalType.TYPE_NEW_SPOT_MARKET]: 'NewSpotMarket',
[Types.ProposalType.TYPE_NEW_TRANSFER]: 'NewTransfer',
[Types.ProposalType.TYPE_UPDATE_ASSET]: 'UpdateAsset',
[Types.ProposalType.TYPE_UPDATE_MARKET]: 'UpdateMarket',
[Types.ProposalType.TYPE_UPDATE_MARKET_STATE]: 'UpdateMarketState',
[Types.ProposalType.TYPE_UPDATE_REFERRAL_PROGRAM]: 'UpdateReferralProgram',
[Types.ProposalType.TYPE_UPDATE_SPOT_MARKET]: 'UpdateSpotMarket',
[Types.ProposalType.TYPE_UPDATE_VOLUME_DISCOUNT_PROGRAM]:
'UpdateVolumeDiscountProgram',
};
const matchFilter = (
data: MarketViewProposalFieldsFragment,
variables: MarketViewProposalsQueryVariables
) => {
return (
(!variables.inState || data.state === variables.inState) &&
(!variables.proposalType ||
data.terms.change.__typename === ProposalTypeMap[variables.proposalType])
);
};
export const update: Update<
MarketViewProposalFieldsFragment[] | null,
MarketViewProposalFieldsFragment,
MarketViewProposalsQueryVariables
> = (
const update = (
data: MarketViewProposalFieldsFragment[] | null,
delta: MarketViewProposalFieldsFragment,
reload,
variables
delta: MarketViewProposalFieldsFragment
) => {
const updateData = produce(data || [], (draft) => {
const { id } = delta;
const index = draft.findIndex((item) => item.id === id);
const match = matchFilter(delta, variables);
if (index === -1) {
if (match) {
draft.unshift(delta);
}
draft.unshift(delta);
} else {
if (match) {
draft[index] = delta;
} else {
draft.splice(index, 1);
}
const currNode = draft[index];
draft[index] = {
...currNode,
...delta,
};
}
});
return updateData;
@@ -109,15 +66,12 @@ const getMarketProposalsData = (
responseData: MarketViewProposalsQuery | null
) => removePaginationWrapper(responseData?.proposalsConnection?.edges) || [];
const subscriptionVariables: MarketViewLiveProposalsSubscriptionVariables = {};
export const marketViewProposalsDataProvider = makeDataProvider<
MarketViewProposalsQuery,
MarketViewProposalFieldsFragment[],
MarketViewLiveProposalsSubscription,
MarketViewProposalFieldsFragment,
MarketViewProposalsQueryVariables,
MarketViewLiveProposalsSubscriptionVariables
MarketViewProposalsQueryVariables
>({
query: MarketViewProposalsDocument,
subscriptionQuery: MarketViewLiveProposalsDocument,
@@ -125,5 +79,4 @@ export const marketViewProposalsDataProvider = makeDataProvider<
getDelta: (subscriptionData: MarketViewLiveProposalsSubscription) =>
subscriptionData.proposals,
getData: getMarketProposalsData,
getSubscriptionVariables: () => subscriptionVariables,
});
@@ -7,3 +7,4 @@ export * from './use-update-network-paramaters-toasts';
export * from './use-successor-market-proposal-details';
export * from './use-new-transfer-proposal-details';
export * from './use-cancel-transfer-proposal-details';
export * from './use-market-view-proposals';
@@ -0,0 +1,29 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketViewProposalsDataProvider } from '../proposals-data-provider';
import type * as Types from '@vegaprotocol/types';
export const useMarketViewProposals = ({
skip = false,
typename,
proposalType,
inState,
}: {
typename: Types.ProposalChange['__typename'];
skip?: boolean;
inState: Types.ProposalState;
proposalType?: Types.ProposalType;
}) => {
const variables = {
inState,
...(proposalType ? { proposalType } : null),
};
const { data } = useDataProvider({
dataProvider: marketViewProposalsDataProvider,
skip,
variables,
});
return (data || []).filter(
(item) => item.terms.change.__typename === typename
);
};
@@ -1,9 +1,9 @@
import { proposalsDataProvider } from '..';
import * as Schema from '@vegaprotocol/types';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useMemo } from 'react';
import first from 'lodash/first';
import { proposalsDataProvider } from '../proposals-data-provider';
import type { ProposalListFieldsFragment } from '../proposals-data-provider';
import type { ProposalListFieldsFragment } from '..';
type UseUpdateProposalProps = {
id?: string;
@@ -58,7 +58,7 @@ type ThemeStore = {
setTheme: (theme?: Theme) => void;
};
const useThemeStore = create<ThemeStore>()((set) => ({
const useThemeStore = create<ThemeStore>((set) => ({
theme: getCurrentTheme(),
setTheme: (newTheme) => {
set((state) => {
+48 -243
View File
@@ -68,13 +68,9 @@ export type AccountEvent = {
/** Filter input for historical balance queries */
export type AccountFilter = {
/** Restrict accounts to those connected to any of the types in this list. Pass an empty list for no filter. */
accountTypes?: InputMaybe<Array<AccountType>>;
/** Restrict accounts to those holding balances in this asset ID. */
assetId?: InputMaybe<Scalars['ID']>;
/** Restrict accounts to those connected to the markets in this list. Pass an empty list for no filter. */
marketIds?: InputMaybe<Array<Scalars['ID']>>;
/** Restrict accounts to those owned by the parties in this list. Pass an empty list for no filter. */
partyIds?: InputMaybe<Array<Scalars['ID']>>;
};
@@ -514,7 +510,7 @@ export type CurrentReferralProgram = {
__typename?: 'CurrentReferralProgram';
/** Defined tiers in increasing order. First element will give Tier 1, second element will give Tier 2, etc. */
benefitTiers: Array<BenefitTier>;
/** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the program will end and benefits will be disabled. */
/** Timestamp as RFC3339Nano, after which when the current epoch ends, the program will end and benefits will be disabled. */
endOfProgramTimestamp: Scalars['Timestamp'];
/** Timestamp as RFC3339Nano when the program ended. If present, the current program has ended and no program is currently running. */
endedAt?: Maybe<Scalars['Timestamp']>;
@@ -1276,44 +1272,6 @@ export type Fees = {
factors: FeeFactors;
};
/** Fees that have been applied on a specific market/asset up to the given epoch. */
export type FeesStats = {
__typename?: 'FeesStats';
/** The settlement asset of the market. */
assetId: Scalars['String'];
/** The epoch for which these stats were valid. */
epoch: Scalars['Int'];
/** The total maker fees generated by all parties. */
makerFeesGenerated: Array<MakerFeesGenerated>;
/** The market the fees were paid in */
marketId: Scalars['String'];
/** The total referral discounts applied to all referee taker fees */
refereesDiscountApplied: Array<PartyAmount>;
/** The total referral rewards generated by all referee taker fees. */
referrerRewardsGenerated: Array<ReferrerRewardsGenerated>;
/** The total maker fees received by the maker side. */
totalMakerFeesReceived: Array<PartyAmount>;
/** The total referral rewards received by referrer of the referral set. */
totalRewardsReceived: Array<PartyAmount>;
/** The total volume discounts applied to all referee taker fees */
volumeDiscountApplied: Array<PartyAmount>;
};
/** Fees that have been applied on a specific asset for a given party. */
export type FeesStatsForParty = {
__typename?: 'FeesStatsForParty';
/** The settlement asset of the market. */
assetId: Scalars['String'];
/** The total referral discounts applied to all referee taker fees */
refereesDiscountApplied: Scalars['String'];
/** The total maker fees received by the maker side. */
totalMakerFeesReceived: Scalars['String'];
/** The total referral rewards received by referrer of the referral set. */
totalRewardsReceived: Scalars['String'];
/** The total volume discounts applied to all referee taker fees */
volumeDiscountApplied: Scalars['String'];
};
/**
* Filter describes the conditions under which oracle data is considered of
* interest or not.
@@ -1638,19 +1596,11 @@ export enum LedgerEntryField {
TransferType = 'TransferType'
}
/** Filter for historical entry ledger queries, you must provide at least one party in FromAccountFilter, or ToAccountFilter */
/** Filter for historical entry ledger queries */
export type LedgerEntryFilter = {
/**
* Determines whether an entry must have accounts matching both the account_from_filter
* and the account_to_filter. If set to 'true', entries must have matches in both filters.
* If set to `false`, entries matching only the account_from_filter or the account_to_filter will also be included.
*/
CloseOnAccountFilters?: InputMaybe<Scalars['Boolean']>;
/** Used to set values for filtering sender accounts. Party must be provided in this filter or to_account_filter, or both. */
FromAccountFilter?: InputMaybe<AccountFilter>;
/** Used to set values for filtering receiver accounts. Party must be provided in this filter or from_account_filter, or both. */
ToAccountFilter?: InputMaybe<AccountFilter>;
/** List of transfer types that is used for filtering sender and receiver accounts. */
TransferTypes?: InputMaybe<Array<InputMaybe<TransferType>>>;
};
@@ -1778,31 +1728,31 @@ export type LiquidityProvision = {
__typename?: 'LiquidityProvision';
/** A set of liquidity buy orders to meet the liquidity provision obligation. */
buys: Array<LiquidityOrderReference>;
/** Specified as a unitless number that represents the amount of the market's settlement asset for the commitment. */
/** Specified as a unit-less number that represents the amount of settlement asset of the market. */
commitmentAmount: Scalars['String'];
/** RFC3339Nano time when the liquidity provision was initially created */
createdAt: Scalars['Timestamp'];
/** Provider's nominated liquidity fee factor, which is an input to the calculation of liquidity fees on the market, as per setting fees and rewarding liquidity providers. */
/** Nominated liquidity fee factor, which is an input to the calculation of liquidity fees on the market, as per setting fees and rewarding liquidity providers. */
fee: Scalars['String'];
/** Unique identifier for the provision (set by the system after consensus) */
/** Unique identifier for the order (set by the system after consensus) */
id: Scalars['ID'];
/** Market ID for the liquidity provision */
/** Market for the order */
market: Market;
/** The party making this commitment */
party: Party;
/** A reference for the orders created to support this liquidity provision */
/** A reference for the orders created out of this liquidity provision */
reference?: Maybe<Scalars['String']>;
/** A set of liquidity sell orders to meet the liquidity provision obligation. */
sells: Array<LiquidityOrderReference>;
/** The current status of this liquidity provision */
status: LiquidityProvisionStatus;
/** RFC3339Nano time when the liquidity provision was updated */
/** RFC3339Nano time of when the liquidity provision was updated */
updatedAt?: Maybe<Scalars['Timestamp']>;
/** The version of this liquidity provision */
version: Scalars['String'];
};
/** Status of a liquidity provision */
/** Status of a liquidity provision order */
export enum LiquidityProvisionStatus {
/** An active liquidity provision */
STATUS_ACTIVE = 'STATUS_ACTIVE',
@@ -1851,20 +1801,6 @@ export type LiquidityProvisionUpdate = {
version: Scalars['String'];
};
export type LiquidityProvisionWithPending = {
__typename?: 'LiquidityProvisionWithPending';
current: LiquidityProvision;
/** Liquidity provision that has been updated by the liquidity provider, and has been accepted by the network, but will not be active until the next epoch. */
pending?: Maybe<LiquidityProvision>;
};
/** Edge type containing the liquidity provision and cursor information returned by a LiquidityProvisionsWithPendingConnection */
export type LiquidityProvisionWithPendingEdge = {
__typename?: 'LiquidityProvisionWithPendingEdge';
cursor: Scalars['String'];
node: LiquidityProvisionWithPending;
};
/** Connection type for retrieving cursor-based paginated liquidity provision information */
export type LiquidityProvisionsConnection = {
__typename?: 'LiquidityProvisionsConnection';
@@ -1879,13 +1815,6 @@ export type LiquidityProvisionsEdge = {
node: LiquidityProvision;
};
/** Connection type for retrieving cursor-based paginated liquidity provision information */
export type LiquidityProvisionsWithPendingConnection = {
__typename?: 'LiquidityProvisionsWithPendingConnection';
edges?: Maybe<Array<Maybe<LiquidityProvisionWithPendingEdge>>>;
pageInfo: PageInfo;
};
export type LiquiditySLAParameters = {
__typename?: 'LiquiditySLAParameters';
/** Specifies the minimum fraction of time LPs must spend 'on the book' providing their committed liquidity */
@@ -1932,15 +1861,6 @@ export type LossSocialization = {
partyId: Scalars['ID'];
};
/** Maker fees generated by the trade aggressor */
export type MakerFeesGenerated = {
__typename?: 'MakerFeesGenerated';
/** Amount of maker fees paid by the taker to the maker */
makerFeesPaid: Array<PartyAmount>;
/** Party that paid the fees */
taker: Scalars['String'];
};
export type MarginCalculator = {
__typename?: 'MarginCalculator';
/** The scaling factors that will be used for margin calculation */
@@ -2059,11 +1979,6 @@ export type Market = {
/** Liquidity monitoring parameters for the market */
liquidityMonitoringParameters: LiquidityMonitoringParameters;
/** The list of the liquidity provision commitments for this market */
liquidityProvisions?: Maybe<LiquidityProvisionsWithPendingConnection>;
/**
* The list of the liquidity provision commitments for this market
* @deprecated Use liquidityProvisions instead
*/
liquidityProvisionsConnection?: Maybe<LiquidityProvisionsConnection>;
/** Optional: Liquidity SLA parameters for the market */
liquiditySLAParameters?: Maybe<LiquiditySLAParameters>;
@@ -2131,14 +2046,6 @@ export type MarketdepthArgs = {
};
/** Represents a product & associated parameters that can be traded on Vega, has an associated OrderBook and Trade history */
export type MarketliquidityProvisionsArgs = {
live?: InputMaybe<Scalars['Boolean']>;
pagination?: InputMaybe<Pagination>;
partyId?: InputMaybe<Scalars['ID']>;
};
/** Represents a product & associated parameters that can be traded on Vega, has an associated OrderBook and Trade history */
export type MarketliquidityProvisionsConnectionArgs = {
live?: InputMaybe<Scalars['Boolean']>;
@@ -3312,39 +3219,6 @@ export type Pagination = {
last?: InputMaybe<Scalars['Int']>;
};
/** Liquidity fees that have been paid to a party in a specific market/asset up to the given epoch. */
export type PaidLiquidityFees = {
__typename?: 'PaidLiquidityFees';
/** The settlement asset of the market. */
assetId: Scalars['String'];
/** The epoch for which these stats were valid. */
epoch: Scalars['Int'];
/** Fees paid per party */
feesPaidPerParty: Array<PartyAmount>;
/** The market the fees were paid in */
marketId: Scalars['String'];
/** Total fees paid across all parties */
totalFeesPaid: Scalars['String'];
};
/** Connection type for retrieving cursor-based paginated paid liquidity fees statistics */
export type PaidLiquidityFeesConnection = {
__typename?: 'PaidLiquidityFeesConnection';
/** The volume discount statistics in this connection */
edges: Array<Maybe<PaidLiquidityFeesEdge>>;
/** The pagination information */
pageInfo: PageInfo;
};
/** Edge type containing the volume discount statistics and cursor information returned by a PaidLiquidityFeesConnection */
export type PaidLiquidityFeesEdge = {
__typename?: 'PaidLiquidityFeesEdge';
/** The cursor for this volume discount statistics */
cursor: Scalars['String'];
/** The volume discount statistics */
node: PaidLiquidityFees;
};
/** Represents a party on Vega, could be an ethereum wallet address in the future */
export type Party = {
__typename?: 'Party';
@@ -3357,12 +3231,7 @@ export type Party = {
depositsConnection?: Maybe<DepositsConnection>;
/** Party identifier */
id: Scalars['ID'];
/** The list of the liquidity provision commitments for this party */
liquidityProvisions?: Maybe<LiquidityProvisionsWithPendingConnection>;
/**
* The list of the liquidity provision commitment for this party
* @deprecated Use liquidityProvisions instead
*/
/** The list of the liquidity provision commitment for this party */
liquidityProvisionsConnection?: Maybe<LiquidityProvisionsConnection>;
/** Margin levels for a market */
marginsConnection?: Maybe<MarginConnection>;
@@ -3385,8 +3254,6 @@ export type Party = {
tradesConnection?: Maybe<TradeConnection>;
/** All transfers for a public key */
transfersConnection?: Maybe<TransferConnection>;
/** The current reward vesting summary of the party for the last epoch */
vestingBalancesSummary: PartyVestingBalancesSummary;
/** All votes on proposals in the Vega network by the given party */
votesConnection?: Maybe<ProposalVoteConnection>;
/** The list of all withdrawals initiated by the party */
@@ -3423,15 +3290,6 @@ export type PartydepositsConnectionArgs = {
};
/** Represents a party on Vega, could be an ethereum wallet address in the future */
export type PartyliquidityProvisionsArgs = {
live?: InputMaybe<Scalars['Boolean']>;
marketId?: InputMaybe<Scalars['ID']>;
pagination?: InputMaybe<Pagination>;
reference?: InputMaybe<Scalars['String']>;
};
/** Represents a party on Vega, could be an ethereum wallet address in the future */
export type PartyliquidityProvisionsConnectionArgs = {
live?: InputMaybe<Scalars['Boolean']>;
@@ -3506,12 +3364,6 @@ export type PartytransfersConnectionArgs = {
};
/** Represents a party on Vega, could be an ethereum wallet address in the future */
export type PartyvestingBalancesSummaryArgs = {
assetId?: InputMaybe<Scalars['ID']>;
};
/** Represents a party on Vega, could be an ethereum wallet address in the future */
export type PartyvotesConnectionArgs = {
pagination?: InputMaybe<Pagination>;
@@ -3572,17 +3424,6 @@ export type PartyEdge = {
node: Party;
};
/** A party reward locked balance. */
export type PartyLockedBalance = {
__typename?: 'PartyLockedBalance';
/** The asset locked */
asset: Asset;
/** The amount locked */
balance: Scalars['String'];
/** Epoch in which the funds will be moved to the vesting balance */
untilEpoch: Scalars['Int'];
};
/**
* All staking information related to a Party.
* Contains the current recognised balance by the network and
@@ -3596,26 +3437,6 @@ export type PartyStake = {
linkings?: Maybe<Array<StakeLinking>>;
};
/** A party's reward vesting balance. */
export type PartyVestingBalance = {
__typename?: 'PartyVestingBalance';
/** The asset being vested */
asset: Asset;
/** The amount locked */
balance: Scalars['String'];
};
/** Summary of a party's reward vesting balances. */
export type PartyVestingBalancesSummary = {
__typename?: 'PartyVestingBalancesSummary';
/** The epoch for which this summary is valid */
epoch?: Maybe<Scalars['Int']>;
/** The party's vesting balances */
lockedBalances?: Maybe<Array<PartyLockedBalance>>;
/** The party vesting balances */
vestingBalances?: Maybe<Array<PartyVestingBalance>>;
};
/** Create an order linked to an index rather than a price */
export type PeggedOrder = {
__typename?: 'PeggedOrder';
@@ -4342,10 +4163,6 @@ export type Query = {
estimatePosition?: Maybe<PositionEstimate>;
/** Query for historic ethereum key rotations */
ethereumKeyRotations: EthereumKeyRotationsConnection;
/** Get fees statistics */
feesStats?: Maybe<FeesStats>;
/** Get fees statistics for a given party */
feesStatsForParty?: Maybe<Array<Maybe<FeesStatsForParty>>>;
/** Funding payment for perpetual markets. */
fundingPayments: FundingPaymentConnection;
/**
@@ -4361,14 +4178,7 @@ export type Query = {
keyRotationsConnection: KeyRotationConnection;
/** The last block process by the blockchain */
lastBlockHeight: Scalars['String'];
/**
* Get ledger entries by asset, market, party, account type, transfer type within the given date range.
* Note: The date range is restricted to any 5 days.
* If no start or end date is provided, only ledger entries from the last 5 days will be returned.
* If a start and end date are provided, but the end date is more than 5 days after the start date, only data up to 5 days after the start date will be returned.
* If a start date is provided but no end date, the end date will be set to 5 days after the start date.
* If no start date is provided, but the end date is, the start date will be set to 5 days before the end date.
*/
/** Get ledger entries by asset, market, party, account type, transfer type within the given date range. */
ledgerEntries: AggregatedLedgerEntriesConnection;
/** List all active liquidity providers for a specific market */
liquidityProviders?: Maybe<LiquidityProviderConnection>;
@@ -4406,8 +4216,6 @@ export type Query = {
orderByReference: Order;
/** Order versions (created via amendments if any) found by orderID */
orderVersionsConnection?: Maybe<OrderConnection>;
/** List paid liquidity fees statistics */
paidLiquidityFees?: Maybe<PaidLiquidityFeesConnection>;
/** One or more entities that are trading on the Vega network */
partiesConnection?: Maybe<PartyConnection>;
/** An entity that is trading on the Vega network */
@@ -4422,6 +4230,8 @@ export type Query = {
protocolUpgradeProposals?: Maybe<ProtocolUpgradeProposalConnection>;
/** Flag indicating whether the data-node is ready to begin the protocol upgrade */
protocolUpgradeStatus?: Maybe<ProtocolUpgradeStatus>;
/** Get referrer fee and discount stats */
referralFeeStats?: Maybe<ReferralSetFeeStats>;
referralSetReferees: ReferralSetRefereeConnection;
/** Get referral set statistics */
referralSetStats: ReferralSetStatsConnection;
@@ -4600,24 +4410,6 @@ export type QueryethereumKeyRotationsArgs = {
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryfeesStatsArgs = {
assetId?: InputMaybe<Scalars['ID']>;
epoch?: InputMaybe<Scalars['Int']>;
marketId?: InputMaybe<Scalars['ID']>;
partyId?: InputMaybe<Scalars['ID']>;
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryfeesStatsForPartyArgs = {
assetId?: InputMaybe<Scalars['ID']>;
fromEpoch?: InputMaybe<Scalars['Int']>;
partyId: Scalars['ID'];
toEpoch?: InputMaybe<Scalars['Int']>;
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryfundingPaymentsArgs = {
marketId?: InputMaybe<Scalars['ID']>;
@@ -4765,15 +4557,6 @@ export type QueryorderVersionsConnectionArgs = {
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerypaidLiquidityFeesArgs = {
assetId?: InputMaybe<Scalars['ID']>;
epoch?: InputMaybe<Scalars['Int']>;
marketId?: InputMaybe<Scalars['ID']>;
partyIDs?: InputMaybe<Array<Scalars['String']>>;
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerypartiesConnectionArgs = {
id?: InputMaybe<Scalars['ID']>;
@@ -4817,9 +4600,18 @@ export type QueryprotocolUpgradeProposalsArgs = {
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryreferralFeeStatsArgs = {
assetId?: InputMaybe<Scalars['ID']>;
epoch?: InputMaybe<Scalars['Int']>;
marketId?: InputMaybe<Scalars['ID']>;
referee?: InputMaybe<Scalars['ID']>;
referrer?: InputMaybe<Scalars['ID']>;
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryreferralSetRefereesArgs = {
aggregationDays?: InputMaybe<Scalars['Int']>;
id?: InputMaybe<Scalars['ID']>;
pagination?: InputMaybe<Pagination>;
referee?: InputMaybe<Scalars['ID']>;
@@ -4981,8 +4773,8 @@ export type ReferralProgram = {
__typename?: 'ReferralProgram';
/** Defined tiers in increasing order. First element will give Tier 1, second element will give Tier 2, etc. */
benefitTiers: Array<BenefitTier>;
/** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the program will end and benefits will be disabled. */
endOfProgramTimestamp: Scalars['Timestamp'];
/** Timestamp as RFC3339, after which when the current epoch ends, the programs will end and benefits will be disabled. */
endOfProgramTimestamp: Scalars['String'];
/** Unique ID generated from the proposal that created this program. */
id: Scalars['ID'];
/**
@@ -5028,6 +4820,25 @@ export type ReferralSetEdge = {
node: ReferralSet;
};
/** Referral rewards and discounts that have been applied on a specific market/asset up to the given epoch. */
export type ReferralSetFeeStats = {
__typename?: 'ReferralSetFeeStats';
/** The settlement asset of the market. */
assetId: Scalars['String'];
/** The epoch for which these stats were valid. */
epoch: Scalars['Int'];
/** The market the fees were paid in */
marketId: Scalars['String'];
/** The total referral discounts applied to all referee taker fees */
refereesDiscountApplied: Array<PartyAmount>;
/** The total referral rewards generated by all referee taker fees. */
referrerRewardsGenerated: Array<ReferrerRewardsGenerated>;
/** The total referral rewards paid to the referrer of the referral set. */
totalRewardsPaid: Array<PartyAmount>;
/** The total volume discounts applied to all referee taker fees */
volumeDiscountApplied: Array<PartyAmount>;
};
/** Data relating to referees that have joined a referral set */
export type ReferralSetReferee = {
__typename?: 'ReferralSetReferee';
@@ -5039,10 +4850,6 @@ export type ReferralSetReferee = {
refereeId: Scalars['ID'];
/** Unique ID of the referral set the referee joined. */
referralSetId: Scalars['ID'];
/** Total rewards generated from the referee over the aggregation period, default is 30 days. */
totalRefereeGeneratedRewards: Scalars['String'];
/** Total notional volume of the referee's aggressive trades over the aggregation period, default is 30 days. */
totalRefereeNotionalTakerVolume: Scalars['String'];
};
/** Connection type for retrieving cursor-based paginated information about the referral set referees */
@@ -5119,8 +4926,6 @@ export type Reward = {
asset: Asset;
/** Epoch for which this reward was distributed */
epoch: Epoch;
/** The epoch when the reward is released */
lockedUntilEpoch: Epoch;
/** The market ID for which this reward is paid if any */
marketId: Scalars['ID'];
/** Party receiving the reward */
@@ -6346,8 +6151,8 @@ export type UpdateReferralProgram = {
__typename?: 'UpdateReferralProgram';
/** Defined tiers in increasing order. First element will give Tier 1, second element will give Tier 2, etc. */
benefitTiers: Array<BenefitTier>;
/** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the program will end and benefits will be disabled. */
endOfProgramTimestamp: Scalars['Timestamp'];
/** Timestamp as RFC3339, after which when the current epoch ends, the programs will end and benefits will be disabled. */
endOfProgramTimestamp: Scalars['String'];
/**
* Defined staking tiers in increasing order. First element will give Tier 1,
* second element will give Tier 2, and so on. Determines the level of
@@ -6385,7 +6190,7 @@ export type UpdateVolumeDiscountProgram = {
__typename?: 'UpdateVolumeDiscountProgram';
/** The benefit tiers for the program */
benefitTiers: Array<VolumeBenefitTier>;
/** Timestamp as Unix time in nanoseconds, after which program ends. */
/** The end time of the program */
endOfProgramTimestamp: Scalars['Timestamp'];
/** The window length to consider for the volume discount program */
windowLength: Scalars['Int'];
@@ -6414,7 +6219,7 @@ export type VolumeDiscountProgram = {
__typename?: 'VolumeDiscountProgram';
/** Defined tiers in increasing order. First element will give Tier 1, second element will give Tier 2, etc. */
benefitTiers: Array<VolumeBenefitTier>;
/** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the program will end and benefits will be disabled. */
/** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the programs will end and benefits will be disabled. */
endOfProgramTimestamp: Scalars['Timestamp'];
/** Timestamp as RFC3339Nano when the program ended. If present, the current program has ended and no program is currently running. */
endedAt?: Maybe<Scalars['Timestamp']>;