Compare commits

..
32 changed files with 541 additions and 733 deletions
@@ -1,4 +1,4 @@
export type HashProps = React.HTMLProps<HTMLSpanElement> & { export type HashProps = {
text: string; text: string;
truncate?: boolean; truncate?: boolean;
}; };
@@ -2,5 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
export { default as PartyLink } from './party-link/party-link'; export { default as PartyLink } from './party-link/party-link';
export { default as NodeLink } from './node-link/node-link'; export { default as NodeLink } from './node-link/node-link';
export { default as MarketLink } from './market-link/market-link'; export { default as MarketLink } from './market-link/market-link';
export { default as NetworkParameterLink } from './network-parameter-link/network-parameter-link';
export * from './asset-link/asset-link'; export * from './asset-link/asset-link';
@@ -1,30 +0,0 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type NetworkParameterLinkProps = Partial<ComponentProps<typeof Link>> & {
parameter: string;
};
/**
* Links a given network parameter to the relevant page and anchor on the page
*/
const NetworkParameterLink = ({
parameter,
...props
}: NetworkParameterLinkProps) => {
return (
<Link
className="underline"
{...props}
to={`/${Routes.NETWORK_PARAMETERS}#${parameter}`}
>
<Hash text={parameter} />
</Link>
);
};
export default NetworkParameterLink;
@@ -26,7 +26,7 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
>; >;
const base = ENV.dataSources.governanceUrl; const base = ENV.dataSources.governanceUrl;
const label = proposal?.rationale?.title || id; const label = proposal?.rationale.title || id;
return ( return (
<ExternalLink href={`${base}/proposals/${id}`}> <ExternalLink href={`${base}/proposals/${id}`}>
@@ -5,10 +5,5 @@ query ExplorerProposalStatus($id: ID!) {
state state
rejectionReason rejectionReason
} }
... on BatchProposal {
id
state
rejectionReason
}
} }
} }
@@ -8,7 +8,7 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null }; export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql` export const ExplorerProposalStatusDocument = gql`
@@ -19,11 +19,6 @@ export const ExplorerProposalStatusDocument = gql`
state state
rejectionReason rejectionReason
} }
... on BatchProposal {
id
state
rejectionReason
}
} }
} }
`; `;
@@ -1,257 +0,0 @@
import { render, screen } from '@testing-library/react';
import { BatchItem } from './batch-item';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { components } from '../../../../../types/explorer';
type Item = components['schemas']['vegaBatchProposalTermsChange'];
describe('BatchItem', () => {
it('Renders "Unknown proposal type" by default', () => {
const item = {};
render(<BatchItem item={item} />);
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
});
it('Renders "Unknown proposal type" for unknown items', () => {
const item = {
newLochNessMonster: {
location: 'Loch Ness',
},
} as unknown as Item;
render(<BatchItem item={item} />);
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
});
it('Renders "New spot market"', () => {
const item = {
newSpotMarket: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New spot market')).toBeInTheDocument();
});
it('Renders "Cancel transfer"', () => {
const item = {
cancelTransfer: {
changes: {
transferId: 'transfer123',
},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
expect(screen.getByText('transf')).toBeInTheDocument();
});
it('Renders "Cancel transfer" without an id', () => {
const item = {
cancelTransfer: {
changes: {},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
});
it('Renders "New freeform"', () => {
const item = {
newFreeform: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New freeform proposal')).toBeInTheDocument();
});
it('Renders "New market"', () => {
const item = {
newMarket: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New market')).toBeInTheDocument();
});
it('Renders "New transfer"', () => {
const item = {
newTransfer: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New transfer')).toBeInTheDocument();
});
it('Renders "Update asset" with assetId', () => {
const item = {
updateAsset: {
assetId: 'asset123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update asset')).toBeInTheDocument();
expect(screen.getByText('asset123')).toBeInTheDocument();
});
it('Renders "Update asset" even if assetId is not set', () => {
const item = {
updateAsset: {
assetId: undefined,
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update asset')).toBeInTheDocument();
});
it('Renders "Update market state" with marketId', () => {
const item = {
updateMarketState: {
changes: {
marketId: 'market123',
},
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market state')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update market state" even if marketId is not set', () => {
const item = {
updateMarketState: {
changes: {
marketId: undefined,
},
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market state')).toBeInTheDocument();
});
it('Renders "Update network parameter" with parameter', () => {
const item = {
updateNetworkParameter: {
changes: {
key: 'parameter123',
},
},
};
render(
<MockedProvider>
<MemoryRouter>
<BatchItem item={item} />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
expect(screen.getByText('parameter123')).toBeInTheDocument();
});
it('Renders "Update network parameter" even if parameter is not set', () => {
const item = {
updateNetworkParameter: {
changes: {
key: undefined,
},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
});
it('Renders "Update referral program"', () => {
const item = {
updateReferralProgram: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update referral program')).toBeInTheDocument();
});
it('Renders "Update spot market" with marketId', () => {
const item = {
updateSpotMarket: {
marketId: 'market123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update spot market')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update spot market" even if marketId is not set', () => {
const item = {
updateSpotMarket: {
marketId: undefined,
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update spot market')).toBeInTheDocument();
});
it('Renders "Update market" with marketId', () => {
const item = {
updateMarket: {
marketId: 'market123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update market" even if marketId is not set', () => {
const item = {
updateMarket: {
marketId: undefined,
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market')).toBeInTheDocument();
});
it('Renders "Update volume discount program"', () => {
const item = {
updateVolumeDiscountProgram: {},
};
render(<BatchItem item={item} />);
expect(
screen.getByText('Update volume discount program')
).toBeInTheDocument();
});
});
@@ -1,87 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink, NetworkParameterLink } from '../../../links';
import type { components } from '../../../../../types/explorer';
import Hash from '../../../links/hash';
type Item = components['schemas']['vegaBatchProposalTermsChange'];
export interface BatchItemProps {
item: Item;
}
/**
* Produces a one line summary for an item in a batch proposal. Could
* easily be adapted to summarise individual proposals, but there is no
* place for that yet.
*
* Details (like IDs) should be shown and linked if available, but handled
* if not available. This is adequate as the ProposalSummary component contains
* a JSON viewer for the full proposal.
*/
export const BatchItem = ({ item }: BatchItemProps) => {
if (item.cancelTransfer) {
const transferId = item?.cancelTransfer?.changes?.transferId || false;
return (
<span>
{t('Cancel transfer')}&nbsp;
{transferId && (
<Hash className="ml-1" truncate={true} text={transferId} />
)}
</span>
);
} else if (item.newFreeform) {
return <span>{t('New freeform proposal')}</span>;
} else if (item.newMarket) {
return <span>{t('New market')}</span>;
} else if (item.newSpotMarket) {
return <span>{t('New spot market')}</span>;
} else if (item.newTransfer) {
return <span>{t('New transfer')}</span>;
} else if (item.updateAsset) {
const assetId = item?.updateAsset?.assetId || false;
return (
<span>
{t('Update asset')}
{assetId && <AssetLink className="ml-1" assetId={assetId} />}
</span>
);
} else if (item.updateMarket) {
const marketId = item?.updateMarket?.marketId || false;
return (
<span>
{t('Update market')}{' '}
{marketId && <MarketLink className="ml-1" id={marketId} />}
</span>
);
} else if (item.updateMarketState) {
const marketId = item?.updateMarketState?.changes?.marketId || false;
return (
<span>
{t('Update market state')}
{marketId && <MarketLink className="ml-1" id={marketId} />}
</span>
);
} else if (item.updateNetworkParameter) {
const param = item?.updateNetworkParameter?.changes?.key || false;
return (
<span>
{t('Update network parameter')}
{param && <NetworkParameterLink className="ml-1" parameter={param} />}
</span>
);
} else if (item.updateReferralProgram) {
return <span>{t('Update referral program')}</span>;
} else if (item.updateSpotMarket) {
const marketId = item?.updateSpotMarket?.marketId || '';
return (
<span>
{t('Update spot market')}
<MarketLink className="ml-1" id={marketId} />
</span>
);
} else if (item.updateVolumeDiscountProgram) {
return <span>{t('Update volume discount program')}</span>;
}
return <span>{t('Unknown proposal type')}</span>;
};
@@ -1,4 +1,6 @@
import type { ProposalTerms } from '../tx-proposal';
import { useState } from 'react'; import { useState } from 'react';
import type { components } from '../../../../../types/explorer';
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog'; import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
import ProposalLink from '../../../links/proposal-link/proposal-link'; import ProposalLink from '../../../links/proposal-link/proposal-link';
import truncate from 'lodash/truncate'; import truncate from 'lodash/truncate';
@@ -7,12 +9,7 @@ import ReactMarkdown from 'react-markdown';
import { ProposalDate } from './proposal-date'; import { ProposalDate } from './proposal-date';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { ProposalTerms } from '../tx-proposal';
import type { components } from '../../../../../types/explorer';
import { BatchItem } from './batch-item';
type Rationale = components['schemas']['vegaProposalRationale']; type Rationale = components['schemas']['vegaProposalRationale'];
type Batch = components['schemas']['v1BatchProposalSubmissionTerms']['changes'];
type ProposalTermsDialog = { type ProposalTermsDialog = {
open: boolean; open: boolean;
@@ -24,7 +21,6 @@ interface ProposalSummaryProps {
id: string; id: string;
rationale?: Rationale; rationale?: Rationale;
terms?: ProposalTerms; terms?: ProposalTerms;
batch?: Batch;
} }
/** /**
@@ -35,7 +31,6 @@ export const ProposalSummary = ({
id, id,
rationale, rationale,
terms, terms,
batch,
}: ProposalSummaryProps) => { }: ProposalSummaryProps) => {
const [dialog, setDialog] = useState<ProposalTermsDialog>({ const [dialog, setDialog] = useState<ProposalTermsDialog>({
open: false, open: false,
@@ -77,18 +72,6 @@ export const ProposalSummary = ({
</ReactMarkdown> </ReactMarkdown>
</div> </div>
)} )}
{batch && (
<section className="pt-2 text-sm leading-tight my-3">
<h2 className="text-lg pb-1">{t('Changes')}</h2>
<ol>
{batch.map((change, index) => (
<li className="ml-4 list-decimal" key={`batch-${index}`}>
<BatchItem item={change} />
</li>
))}
</ol>
</section>
)}
<div className="pt-5"> <div className="pt-5">
<button className="underline max-md:hidden mr-5" onClick={openDialog}> <button className="underline max-md:hidden mr-5" onClick={openDialog}>
{t('View terms')} {t('View terms')}
@@ -1,75 +0,0 @@
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ProposalSummary } from './proposal/summary';
import Hash from '../../links/hash';
import { t } from '@vegaprotocol/i18n';
export type Proposal = components['schemas']['v1BatchProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
interface TxBatchProposalProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
*
*/
export const TxBatchProposal = ({
txData,
pubKey,
blockData,
}: TxBatchProposalProps) => {
if (!txData || !txData.command.batchProposalSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
let deterministicId = '';
const proposal: Proposal = txData.command.batchProposalSubmission;
const sig = txData?.signature?.value;
if (sig) {
deterministicId = txSignatureToDeterministicId(sig);
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{t('Batch proposal')}</TableCell>
</TableRow>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
hideTypeRow={true}
/>
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
{proposal.terms?.changes?.length || t('No changes')}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>
<Hash text={deterministicId} />
</TableCell>
</TableRow>
</TableWithTbody>
{proposal && (
<ProposalSummary
id={deterministicId}
rationale={proposal?.rationale}
terms={proposal.terms}
batch={proposal.terms?.changes}
/>
)}
</>
);
};
@@ -33,7 +33,6 @@ import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set'; import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
import { TxDetailsJoinTeam } from './tx-join-team'; import { TxDetailsJoinTeam } from './tx-join-team';
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode'; import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
import { TxBatchProposal } from './tx-batch-proposal';
interface TxDetailsWrapperProps { interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -137,8 +136,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsJoinTeam; return TxDetailsJoinTeam;
case 'Update Margin Mode': case 'Update Margin Mode':
return TxDetailsUpdateMarginMode; return TxDetailsUpdateMarginMode;
case 'Batch Proposal':
return TxBatchProposal;
default: default:
return TxDetailsGeneric; return TxDetailsGeneric;
} }
@@ -20,7 +20,6 @@ export type FilterOption =
| 'Amend Order' | 'Amend Order'
| 'Apply Referral Code' | 'Apply Referral Code'
| 'Batch Market Instructions' | 'Batch Market Instructions'
| 'Batch Proposal'
| 'Cancel LiquidityProvision Order' | 'Cancel LiquidityProvision Order'
| 'Cancel Order' | 'Cancel Order'
| 'Cancel Transfer Funds' | 'Cancel Transfer Funds'
@@ -68,13 +67,7 @@ export const filterOptions: Record<string, FilterOption[]> = {
'Cancel Transfer Funds', 'Cancel Transfer Funds',
'Withdraw', 'Withdraw',
], ],
Governance: [ Governance: ['Delegate', 'Undelegate', 'Vote on Proposal', 'Proposal'],
'Batch Proposal',
'Delegate',
'Undelegate',
'Vote on Proposal',
'Proposal',
],
Referrals: [ Referrals: [
'Apply Referral Code', 'Apply Referral Code',
'Create Referral Set', 'Create Referral Set',
@@ -31,7 +31,7 @@ export const CompetitionsCreateTeam = () => {
<div className="mx-auto md:w-2/3 max-w-xl"> <div className="mx-auto md:w-2/3 max-w-xl">
<Box className="flex flex-col gap-4"> <Box className="flex flex-col gap-4">
<h1 className="calt text-2xl lg:text-3xl xl:text-4xl"> <h1 className="calt text-2xl lg:text-3xl xl:text-4xl">
{t('Create a team')} {isSolo ? t('Create solo team') : t('Create a team')}
</h1> </h1>
{pubKey && !isReadOnly ? ( {pubKey && !isReadOnly ? (
<CreateTeamFormContainer isSolo={isSolo} /> <CreateTeamFormContainer isSolo={isSolo} />
@@ -71,18 +71,27 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
if (status === 'confirmed') { if (status === 'confirmed') {
return ( return (
<div className="flex flex-col items-start gap-2"> <div
className="flex flex-col items-start gap-2"
data-testid="team-creation-success-message"
>
<p className="text-sm">{t('Team creation transaction successful')}</p> <p className="text-sm">{t('Team creation transaction successful')}</p>
{code && ( {code && (
<> <>
<p className="text-sm"> <p className="text-sm">
Your team ID is:{' '} Your team ID is:{' '}
<span className="font-mono break-all">{code}</span> <span
className="font-mono break-all"
data-testid="team-id-display"
>
{code}
</span>
</p> </p>
<TradingAnchorButton <TradingAnchorButton
href={Links.COMPETITIONS_TEAM(code)} href={Links.COMPETITIONS_TEAM(code)}
intent={Intent.Info} intent={Intent.Info}
size="small" size="small"
data-testid="view-team-button"
> >
{t('View team')} {t('View team')}
</TradingAnchorButton> </TradingAnchorButton>
@@ -125,7 +134,7 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
onSubmit={onSubmit} onSubmit={onSubmit}
status={status} status={status}
err={err} err={err}
isSolo={isSolo} isCreatingSoloTeam={isSolo}
/> />
); );
}; };
@@ -31,10 +31,7 @@ export const CompetitionsHome = () => {
currentEpoch, currentEpoch,
}); });
const { data: teamsData, loading: teamsLoading } = useTeams({ const { data: teamsData, loading: teamsLoading } = useTeams();
sortByField: ['totalQuantumRewards'],
order: 'desc',
});
return ( return (
<ErrorBoundary> <ErrorBoundary>
@@ -63,6 +60,7 @@ export const CompetitionsHome = () => {
e.preventDefault(); e.preventDefault();
navigate(Links.COMPETITIONS_CREATE_TEAM()); navigate(Links.COMPETITIONS_CREATE_TEAM());
}} }}
data-testid="create-public-team-button"
> >
{t('Create a public team')} {t('Create a public team')}
</TradingButton> </TradingButton>
@@ -81,6 +79,7 @@ export const CompetitionsHome = () => {
e.preventDefault(); e.preventDefault();
navigate(Links.COMPETITIONS_CREATE_TEAM_SOLO()); navigate(Links.COMPETITIONS_CREATE_TEAM_SOLO());
}} }}
data-testid="create-private-team-button"
> >
{t('Create a private team')} {t('Create a private team')}
</TradingButton> </TradingButton>
@@ -99,6 +98,7 @@ export const CompetitionsHome = () => {
e.preventDefault(); e.preventDefault();
navigate(Links.COMPETITIONS_TEAMS()); navigate(Links.COMPETITIONS_TEAMS());
}} }}
data-testid="choose-team-button"
> >
{t('Choose a team')} {t('Choose a team')}
</TradingButton> </TradingButton>
@@ -38,12 +38,11 @@ export const CompetitionsTeam = () => {
const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => { const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
const t = useT(); const t = useT();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const { team, partyTeam, stats, members, games, loading, refetch } = useTeam( const { data, team, partyTeam, stats, members, games, loading, refetch } =
teamId, useTeam(teamId, pubKey || undefined);
pubKey || undefined
);
if (loading) { // only show spinner on first load so when users join teams its smoother
if (!data && loading) {
return ( return (
<Splash> <Splash>
<Loader /> <Loader />
@@ -100,8 +99,10 @@ const TeamPage = ({
> >
{team.name} {team.name}
</h1> </h1>
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} /> <div className="flex gap-2">
<UpdateTeamButton team={team} /> <JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} />
</div>
</div> </div>
</header> </header>
<TeamStats stats={stats} members={members} games={games} /> <TeamStats stats={stats} members={members} games={games} />
@@ -184,7 +185,10 @@ const Members = ({ members }: { members?: Member[] }) => {
const data = orderBy( const data = orderBy(
members.map((m) => ({ members.map((m) => ({
referee: <RefereeLink pubkey={m.referee} />, referee: <RefereeLink pubkey={m.referee} isCreator={m.isCreator} />,
rewards: formatNumber(m.totalQuantumRewards),
volume: formatNumber(m.totalQuantumVolume),
gamesPlayed: formatNumber(m.totalGamesPlayed),
joinedAt: getDateTimeFormat().format(new Date(m.joinedAt)), joinedAt: getDateTimeFormat().format(new Date(m.joinedAt)),
joinedAtEpoch: Number(m.joinedAtEpoch), joinedAtEpoch: Number(m.joinedAtEpoch),
})), })),
@@ -195,7 +199,10 @@ const Members = ({ members }: { members?: Member[] }) => {
return ( return (
<Table <Table
columns={[ columns={[
{ name: 'referee', displayName: t('Referee') }, { name: 'referee', displayName: t('Member ID') },
{ name: 'rewards', displayName: t('Rewards earned') },
{ name: 'volume', displayName: t('Total volume') },
{ name: 'gamesPlayed', displayName: t('Games played') },
{ {
name: 'joinedAt', name: 'joinedAt',
displayName: t('Joined at'), displayName: t('Joined at'),
@@ -211,14 +218,24 @@ const Members = ({ members }: { members?: Member[] }) => {
); );
}; };
const RefereeLink = ({ pubkey }: { pubkey: string }) => { const RefereeLink = ({
pubkey,
isCreator,
}: {
pubkey: string;
isCreator: boolean;
}) => {
const t = useT();
const linkCreator = useLinks(DApp.Explorer); const linkCreator = useLinks(DApp.Explorer);
const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey)); const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey));
return ( return (
<Link to={link} target="_blank" className="underline underline-offset-4"> <>
{truncateMiddle(pubkey)} <Link to={link} target="_blank" className="underline underline-offset-4">
</Link> {truncateMiddle(pubkey)}
</Link>{' '}
<span className="text-muted text-xs">{isCreator ? t('Owner') : ''}</span>
</>
); );
}; };
@@ -17,10 +17,7 @@ export const CompetitionsTeams = () => {
usePageTitle([t('Competitions'), t('Teams')]); usePageTitle([t('Competitions'), t('Teams')]);
const { data: teamsData, loading: teamsLoading } = useTeams({ const { data: teamsData, loading: teamsLoading } = useTeams();
sortByField: ['totalQuantumRewards'],
order: 'desc',
});
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [filter, setFilter] = useState<string | null | undefined>(undefined); const [filter, setFilter] = useState<string | null | undefined>(undefined);
@@ -98,7 +98,7 @@ const UpdateTeamFormContainer = ({
type={TransactionType.UpdateReferralSet} type={TransactionType.UpdateReferralSet}
status={status} status={status}
err={err} err={err}
isSolo={team.closed} isCreatingSoloTeam={team.closed}
onSubmit={onSubmit} onSubmit={onSubmit}
defaultValues={defaultValues} defaultValues={defaultValues}
/> />
@@ -6,11 +6,7 @@ import {
VegaIcon, VegaIcon,
VegaIconNames, VegaIconNames,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { import { useSimpleTransaction, useVegaWallet } from '@vegaprotocol/wallet';
useSimpleTransaction,
useVegaWallet,
type Status,
} from '@vegaprotocol/wallet';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { type Team } from '../../lib/hooks/use-team'; import { type Team } from '../../lib/hooks/use-team';
import { useState } from 'react'; import { useState } from 'react';
@@ -27,19 +23,8 @@ export const JoinTeam = ({
refetch: () => void; refetch: () => void;
}) => { }) => {
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
const { send, status } = useSimpleTransaction({
onSuccess: refetch,
});
const [confirmDialog, setConfirmDialog] = useState<JoinType>(); const [confirmDialog, setConfirmDialog] = useState<JoinType>();
const joinTeam = () => {
send({
joinTeam: {
id: team.teamId,
},
});
};
return ( return (
<> <>
<JoinButton <JoinButton
@@ -56,11 +41,10 @@ export const JoinTeam = ({
{confirmDialog !== undefined && ( {confirmDialog !== undefined && (
<DialogContent <DialogContent
type={confirmDialog} type={confirmDialog}
status={status}
team={team} team={team}
partyTeam={partyTeam} partyTeam={partyTeam}
onConfirm={joinTeam}
onCancel={() => setConfirmDialog(undefined)} onCancel={() => setConfirmDialog(undefined)}
refetch={refetch}
/> />
)} )}
</Dialog> </Dialog>
@@ -110,7 +94,7 @@ export const JoinButton = ({
// Not creator of the team, but still can't switch because // Not creator of the team, but still can't switch because
// creators cannot leave their own team // creators cannot leave their own team
return ( return (
<Tooltip description="As a team creator, you cannot switch teams"> <Tooltip description={t('As a team creator, you cannot switch teams')}>
<Button intent={Intent.Primary} disabled={true}> <Button intent={Intent.Primary} disabled={true}>
{t('Switch team')}{' '} {t('Switch team')}{' '}
</Button> </Button>
@@ -121,7 +105,11 @@ export const JoinButton = ({
// Party is in a team, but not this one // Party is in a team, but not this one
else if (partyTeam && partyTeam.teamId !== team.teamId) { else if (partyTeam && partyTeam.teamId !== team.teamId) {
return ( return (
<Button onClick={() => onJoin('switch')} intent={Intent.Primary}> <Button
onClick={() => onJoin('switch')}
intent={Intent.Primary}
data-testid="switch-team-button"
>
{t('Switch team')}{' '} {t('Switch team')}{' '}
</Button> </Button>
); );
@@ -149,21 +137,39 @@ export const JoinButton = ({
const DialogContent = ({ const DialogContent = ({
type, type,
status,
team, team,
partyTeam, partyTeam,
onConfirm,
onCancel, onCancel,
refetch,
}: { }: {
type: JoinType; type: JoinType;
status: Status;
team: Team; team: Team;
partyTeam?: Team; partyTeam?: Team;
onConfirm: () => void;
onCancel: () => void; onCancel: () => void;
refetch: () => void;
}) => { }) => {
const t = useT(); const t = useT();
const { send, status, error } = useSimpleTransaction({
onSuccess: refetch,
});
const joinTeam = () => {
send({
joinTeam: {
id: team.teamId,
},
});
};
if (error) {
return (
<p className="text-vega-red break-words first-letter:capitalize">
{error}
</p>
);
}
if (status === 'requested') { if (status === 'requested') {
return <p>{t('Confirm in wallet...')}</p>; return <p>{t('Confirm in wallet...')}</p>;
} }
@@ -213,7 +219,11 @@ const DialogContent = ({
</> </>
)} )}
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<Button onClick={onConfirm} intent={Intent.Success}> <Button
onClick={joinTeam}
intent={Intent.Success}
data-testid="confirm-switch-button"
>
{t('Confirm')} {t('Confirm')}
</Button> </Button>
<Button onClick={onCancel} intent={Intent.Danger}> <Button onClick={onCancel} intent={Intent.Danger}>
@@ -6,6 +6,8 @@ import {
TextArea, TextArea,
TradingButton, TradingButton,
Intent, Intent,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { URL_REGEX, isValidVegaPublicKey } from '@vegaprotocol/utils'; import { URL_REGEX, isValidVegaPublicKey } from '@vegaprotocol/utils';
@@ -17,6 +19,8 @@ import type {
UpdateReferralSet, UpdateReferralSet,
Status, Status,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import classNames from 'classnames';
import { useLayoutEffect, useState } from 'react';
export type FormFields = { export type FormFields = {
id: string; id: string;
@@ -28,8 +32,8 @@ export type FormFields = {
}; };
export enum TransactionType { export enum TransactionType {
CreateReferralSet, CreateReferralSet = 'CreateReferralSet',
UpdateReferralSet, UpdateReferralSet = 'UpdateReferralSet',
} }
const prepareTransaction = ( const prepareTransaction = (
@@ -75,14 +79,14 @@ export const TeamForm = ({
type, type,
status, status,
err, err,
isSolo, isCreatingSoloTeam,
onSubmit, onSubmit,
defaultValues, defaultValues,
}: { }: {
type: TransactionType; type: TransactionType;
status: ReturnType<typeof useReferralSetTransaction>['status']; status: ReturnType<typeof useReferralSetTransaction>['status'];
err: ReturnType<typeof useReferralSetTransaction>['err']; err: ReturnType<typeof useReferralSetTransaction>['err'];
isSolo: boolean; isCreatingSoloTeam: boolean;
onSubmit: ReturnType<typeof useReferralSetTransaction>['onSubmit']; onSubmit: ReturnType<typeof useReferralSetTransaction>['onSubmit'];
defaultValues?: FormFields; defaultValues?: FormFields;
}) => { }) => {
@@ -96,7 +100,7 @@ export const TeamForm = ({
formState: { errors }, formState: { errors },
} = useForm<FormFields>({ } = useForm<FormFields>({
defaultValues: { defaultValues: {
private: isSolo, private: isCreatingSoloTeam,
...defaultValues, ...defaultValues,
}, },
}); });
@@ -109,16 +113,14 @@ export const TeamForm = ({
return ( return (
<form onSubmit={handleSubmit(sendTransaction)}> <form onSubmit={handleSubmit(sendTransaction)}>
<input <input type="hidden" {...register('id')} />
type="hidden"
{...register('id', {
disabled: true,
})}
/>
<TradingFormGroup label={t('Team name')} labelFor="name"> <TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput {...register('name', { required: t('Required') })} /> <TradingInput
{...register('name', { required: t('Required') })}
data-testid="team-name-input"
/>
{errors.name?.message && ( {errors.name?.message && (
<TradingInputError forInput="name"> <TradingInputError forInput="name" data-testid="team-name-error">
{errors.name.message} {errors.name.message}
</TradingInputError> </TradingInputError>
)} )}
@@ -134,9 +136,10 @@ export const TeamForm = ({
{...register('url', { {...register('url', {
pattern: { value: URL_REGEX, message: t('Invalid URL') }, pattern: { value: URL_REGEX, message: t('Invalid URL') },
})} })}
data-testid="team-url-input"
/> />
{errors.url?.message && ( {errors.url?.message && (
<TradingInputError forInput="url"> <TradingInputError forInput="url" data-testid="team-url-error">
{errors.url.message} {errors.url.message}
</TradingInputError> </TradingInputError>
)} )}
@@ -153,66 +156,86 @@ export const TeamForm = ({
message: t('Invalid image URL'), message: t('Invalid image URL'),
}, },
})} })}
data-testid="avatar-url-input"
/> />
{errors.avatarUrl?.message && ( {errors.avatarUrl?.message && (
<TradingInputError forInput="avatarUrl"> <TradingInputError
forInput="avatarUrl"
data-testid="avatar-url-error"
>
{errors.avatarUrl.message} {errors.avatarUrl.message}
</TradingInputError> </TradingInputError>
)} )}
</TradingFormGroup> </TradingFormGroup>
<TradingFormGroup {
label={t('Make team private')} // allow changing to private/public if editing, but don't show these options if making a solo team
labelFor="private" (type === TransactionType.UpdateReferralSet || !isCreatingSoloTeam) && (
hideLabel={true} <>
> <TradingFormGroup
<Controller label={t('Make team private')}
name="private" labelFor="private"
control={control} hideLabel={true}
render={({ field }) => { >
return ( <Controller
<TradingCheckbox name="private"
label={t('Make team private')} control={control}
checked={field.value} render={({ field }) => {
onCheckedChange={(value) => { return (
field.onChange(value); <TradingCheckbox
label={t('Make team private')}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
}}
data-testid="team-private-checkbox"
/>
);
}} }}
disabled={isSolo}
/> />
); </TradingFormGroup>
}} {isPrivate && (
/> <TradingFormGroup
</TradingFormGroup> label={t('Public key allow list')}
{isPrivate && ( labelFor="allowList"
<TradingFormGroup labelDescription={t(
label={t('Public key allow list')} 'Use a comma separated list to allow only specific public keys to join the team'
labelFor="allowList" )}
labelDescription={t( >
'Use a comma separated list to allow only specific public keys to join the team' <TextArea
)} {...register('allowList', {
> required: t('Required'),
<TextArea validate: {
{...register('allowList', { allowList: (value) => {
required: t('Required'), const publicKeys = parseAllowListText(value);
disabled: isSolo, if (
validate: { publicKeys.every((pk) => isValidVegaPublicKey(pk))
allowList: (value) => { ) {
const publicKeys = parseAllowListText(value); return true;
if (publicKeys.every((pk) => isValidVegaPublicKey(pk))) { }
return true; return t('Invalid public key found in allow list');
} },
return t('Invalid public key found in allow list'); },
}, })}
}, data-testid="team-allow-list-textarea"
})} />
/> {errors.allowList?.message && (
{errors.allowList?.message && ( <TradingInputError
<TradingInputError forInput="avatarUrl"> forInput="avatarUrl"
{errors.allowList.message} data-testid="team-allow-list-error"
</TradingInputError> >
)} {errors.allowList.message}
</TradingFormGroup> </TradingInputError>
)}
</TradingFormGroup>
)}
</>
)
}
{err && (
<p className="text-danger text-xs mb-4 first-letter:capitalize">
{err}
</p>
)} )}
{err && <p className="text-danger text-xs mb-4 capitalize">{err}</p>}
<SubmitButton type={type} status={status} /> <SubmitButton type={type} status={status} />
</form> </form>
); );
@@ -233,20 +256,61 @@ const SubmitButton = ({
text = t('Update'); text = t('Update');
} }
let confirmedText = t('Created');
if (type === TransactionType.UpdateReferralSet) {
confirmedText = t('Updated');
}
if (status === 'requested') { if (status === 'requested') {
text = t('Confirm in wallet...'); text = t('Confirm in wallet...');
} else if (status === 'pending') { } else if (status === 'pending') {
text = t('Confirming transaction...'); text = t('Confirming transaction...');
} }
const [showConfirmed, setShowConfirmed] = useState<boolean>(false);
useLayoutEffect(() => {
let to: ReturnType<typeof setTimeout>;
if (status === 'confirmed' && !showConfirmed) {
to = setTimeout(() => {
setShowConfirmed(true);
}, 100);
}
return () => {
clearTimeout(to);
};
}, [showConfirmed, status]);
const confirmed = (
<span
className={classNames('text-sm transition-opacity opacity-0', {
'opacity-100': showConfirmed,
})}
>
<VegaIcon
name={VegaIconNames.TICK}
size={18}
className="text-vega-green-500"
/>{' '}
{confirmedText}
</span>
);
return ( return (
<TradingButton type="submit" intent={Intent.Info} disabled={disabled}> <div className="flex gap-2 items-baseline">
{text} <TradingButton
</TradingButton> type="submit"
intent={Intent.Info}
disabled={disabled}
data-testid="team-form-submit-button"
>
{text}
</TradingButton>
{status === 'confirmed' && confirmed}
</div>
); );
}; };
const parseAllowListText = (str: string) => { const parseAllowListText = (str: string = '') => {
return str return str
.split(',') .split(',')
.map((v) => v.trim()) .map((v) => v.trim())
@@ -2,8 +2,10 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import { type Team } from '../../lib/hooks/use-team'; import { type Team } from '../../lib/hooks/use-team';
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit'; import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links } from '../../lib/links'; import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t';
export const UpdateTeamButton = ({ team }: { team: Team }) => { export const UpdateTeamButton = ({ team }: { team: Team }) => {
const t = useT();
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
if (pubKey && !isReadOnly && pubKey === team.referrer) { if (pubKey && !isReadOnly && pubKey === team.referrer) {
@@ -12,7 +14,9 @@ export const UpdateTeamButton = ({ team }: { team: Team }) => {
data-testid="update-team-button" data-testid="update-team-button"
href={Links.COMPETITIONS_UPDATE_TEAM(team.teamId)} href={Links.COMPETITIONS_UPDATE_TEAM(team.teamId)}
intent={Intent.Info} intent={Intent.Info}
/> >
{t('Update team')}
</TradingAnchorButton>
); );
} }
@@ -152,7 +152,11 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
if (!enrichedTransfers || !enrichedTransfers.length) return null; if (!enrichedTransfers || !enrichedTransfers.length) return null;
return ( return (
<Card title={t('Active rewards')} className="lg:col-span-full"> <Card
title={t('Active rewards')}
className="lg:col-span-full"
data-testid="active-rewards-card"
>
{enrichedTransfers.length > 1 && ( {enrichedTransfers.length > 1 && (
<TradingInput <TradingInput
onChange={(e) => onChange={(e) =>
@@ -371,6 +375,7 @@ export const ActiveRewardCard = ({
'rounded-lg', 'rounded-lg',
gradientClassName gradientClassName
)} )}
data-testid="active-rewards-card"
> >
<div <div
className={classNames( className={classNames(
@@ -382,7 +387,7 @@ export const ActiveRewardCard = ({
<div className="flex flex-col gap-2 items-center text-center"> <div className="flex flex-col gap-2 items-center text-center">
<EntityIcon transfer={transfer} /> <EntityIcon transfer={transfer} />
{entityScope && ( {entityScope && (
<span className="text-muted text-xs"> <span className="text-muted text-xs" data-testid="entity-scope">
{EntityScopeLabelMapping[entityScope] || t('Unspecified')} {EntityScopeLabelMapping[entityScope] || t('Unspecified')}
</span> </span>
)} )}
@@ -390,7 +395,7 @@ export const ActiveRewardCard = ({
<div className="flex flex-col gap-2 items-center text-center"> <div className="flex flex-col gap-2 items-center text-center">
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center"> <h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
<span className="font-glitch"> <span className="font-glitch" data-testid="reward-value">
{addDecimalsFormatNumber( {addDecimalsFormatNumber(
transferNode.transfer.amount, transferNode.transfer.amount,
transferNode.transfer.asset?.decimals || 0, transferNode.transfer.asset?.decimals || 0,
@@ -411,7 +416,7 @@ export const ActiveRewardCard = ({
)} )}
underline={true} underline={true}
> >
<span className="text-xs"> <span className="text-xs" data-testid="distribution-strategy">
{ {
DistributionStrategyMapping[ DistributionStrategyMapping[
dispatchStrategy.distributionStrategy dispatchStrategy.distributionStrategy
@@ -429,7 +434,10 @@ export const ActiveRewardCard = ({
'Number of epochs after distribution to delay vesting of rewards by' 'Number of epochs after distribution to delay vesting of rewards by'
)} )}
/> />
<span className="text-muted text-xs whitespace-nowrap"> <span
className="text-muted text-xs whitespace-nowrap"
data-testid="locked-for"
>
{t('numberEpochs', '{{count}} epochs', { {t('numberEpochs', '{{count}} epochs', {
count: kind.dispatchStrategy?.lockPeriod, count: kind.dispatchStrategy?.lockPeriod,
})} })}
@@ -438,7 +446,7 @@ export const ActiveRewardCard = ({
</div> </div>
<span className="border-[0.5px] border-gray-700" /> <span className="border-[0.5px] border-gray-700" />
<span> <span data-testid="dispatch-metric-info">
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '} {DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '}
<Tooltip <Tooltip
underline={suspended} underline={suspended}
@@ -458,8 +466,8 @@ export const ActiveRewardCard = ({
<div className="flex items-center gap-8 flex-wrap"> <div className="flex items-center gap-8 flex-wrap">
{kind.endEpoch && ( {kind.endEpoch && (
<span className="flex flex-col"> <span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')}</span> <span className="text-muted text-xs">{t('Ends in')} </span>
<span> <span data-testid="ends-in">
{t('numberEpochs', '{{count}} epochs', { {t('numberEpochs', '{{count}} epochs', {
count: kind.endEpoch - currentEpoch, count: kind.endEpoch - currentEpoch,
})} })}
@@ -470,7 +478,7 @@ export const ActiveRewardCard = ({
{ {
<span className="flex flex-col"> <span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span> <span className="text-muted text-xs">{t('Assessed over')}</span>
<span> <span data-testid="assessed-over">
{t('numberEpochs', '{{count}} epochs', { {t('numberEpochs', '{{count}} epochs', {
count: dispatchStrategy.windowLength, count: dispatchStrategy.windowLength,
})} })}
@@ -513,7 +521,7 @@ const RewardRequirements = ({
entity: EntityScopeLabelMapping[dispatchStrategy.entityScope], entity: EntityScopeLabelMapping[dispatchStrategy.entityScope],
})} })}
</dt> </dt>
<dd className="flex items-center gap-1"> <dd className="flex items-center gap-1" data-testid="scope">
<RewardEntityScope dispatchStrategy={dispatchStrategy} /> <RewardEntityScope dispatchStrategy={dispatchStrategy} />
</dd> </dd>
</div> </div>
@@ -522,7 +530,10 @@ const RewardRequirements = ({
<dt className="flex items-center gap-1 text-muted"> <dt className="flex items-center gap-1 text-muted">
{t('Staked VEGA')} {t('Staked VEGA')}
</dt> </dt>
<dd className="flex items-center gap-1"> <dd
className="flex items-center gap-1"
data-testid="staking-requirement"
>
{addDecimalsFormatNumber( {addDecimalsFormatNumber(
dispatchStrategy?.stakingRequirement || 0, dispatchStrategy?.stakingRequirement || 0,
assetDecimalPlaces assetDecimalPlaces
@@ -534,7 +545,7 @@ const RewardRequirements = ({
<dt className="flex items-center gap-1 text-muted"> <dt className="flex items-center gap-1 text-muted">
{t('Average position')} {t('Average position')}
</dt> </dt>
<dd className="flex items-center gap-1"> <dd className="flex items-center gap-1" data-testid="average-position">
{addDecimalsFormatNumber( {addDecimalsFormatNumber(
dispatchStrategy?.notionalTimeWeightedAveragePositionRequirement || dispatchStrategy?.notionalTimeWeightedAveragePositionRequirement ||
0, 0,
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.74.0-preview.7 VEGA_VERSION=v0.74.0-preview.8
LOCAL_SERVER=false LOCAL_SERVER=false
@@ -59,7 +59,7 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
next_epoch(vega=vega) next_epoch(vega=vega)
page.reload() page.reload()
expect(page.locator(".from-vega-cdark-400")).to_be_visible(timeout=15000) expect(page.get_by_test_id("active-rewards-card")).to_be_visible(timeout=15000)
governance.submit_oracle_data( governance.submit_oracle_data(
wallet=vega.wallet, wallet=vega.wallet,
payload={"trading.terminated": "true"}, payload={"trading.terminated": "true"},
@@ -67,4 +67,4 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
) )
next_epoch(vega=vega) next_epoch(vega=vega)
page.reload() page.reload()
expect(page.locator(".from-vega-cdark-400")).not_to_be_in_viewport() expect(page.get_by_test_id("active-rewards-card")).not_to_be_in_viewport()
+153 -42
View File
@@ -3,7 +3,7 @@ from playwright.sync_api import expect, Page
import vega_sim.proto.vega as vega_protos import vega_sim.proto.vega as vega_protos
from vega_sim.null_service import VegaServiceNull from vega_sim.null_service import VegaServiceNull
from conftest import init_vega from conftest import init_vega
from actions.utils import next_epoch from actions.utils import next_epoch, change_keys
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
from conftest import auth_setup, init_page, init_vega, risk_accepted_setup from conftest import auth_setup, init_page, init_vega, risk_accepted_setup
from wallet_config import PARTY_A, PARTY_B, PARTY_C, PARTY_D, MM_WALLET from wallet_config import PARTY_A, PARTY_B, PARTY_C, PARTY_D, MM_WALLET
@@ -14,6 +14,7 @@ def vega(request):
with init_vega(request) as vega: with init_vega(request) as vega:
yield vega yield vega
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def team_page(vega, browser, request, setup_teams_and_games): def team_page(vega, browser, request, setup_teams_and_games):
with init_page(vega, browser, request) as page: with init_page(vega, browser, request) as page:
@@ -23,9 +24,19 @@ def team_page(vega, browser, request, setup_teams_and_games):
page.goto(f"/#/competitions/teams/{team_id}") page.goto(f"/#/competitions/teams/{team_id}")
yield page yield page
@pytest.fixture(scope="module")
def competitions_page(vega, browser, request, setup_teams_and_games):
with init_page(vega, browser, request) as page:
risk_accepted_setup(page)
auth_setup(vega, page)
team_id = setup_teams_and_games["team_id"]
page.goto(f"/#/competitions/")
yield page
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def setup_teams_and_games(vega: VegaServiceNull): def setup_teams_and_games(vega: VegaServiceNull):
tDAI_market = setup_continuous_market(vega) tDAI_market = setup_continuous_market(vega, custom_quantum=100000)
tDAI_asset_id = vega.find_asset_id(symbol="tDAI") tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000) vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000)
vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000) vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000)
@@ -46,6 +57,18 @@ def setup_teams_and_games(vega: VegaServiceNull):
# list_teams actually returns a dictionary {"team_id": Team} # list_teams actually returns a dictionary {"team_id": Team}
team_id = list(teams.keys())[0] team_id = list(teams.keys())[0]
vega.create_referral_set(
key_name="market_maker",
name="test",
team_url="https://vega.xyz",
avatar_url="http://placekitten.com/200/200",
closed=False,
)
next_epoch(vega)
teams = vega.list_teams()
team_id_2 = list(teams.keys())[0]
vega.apply_referral_code("Key 1", team_id_2)
vega.apply_referral_code(PARTY_B.name, team_id) vega.apply_referral_code(PARTY_B.name, team_id)
@@ -63,7 +86,7 @@ def setup_teams_and_games(vega: VegaServiceNull):
current_epoch = vega.statistics().epoch_seq current_epoch = vega.statistics().epoch_seq
game_start = current_epoch + 1 game_start = current_epoch + 1
game_end = current_epoch + 11 game_end = current_epoch + 14
current_epoch = vega.statistics().epoch_seq current_epoch = vega.statistics().epoch_seq
print(f"[EPOCH: {current_epoch}] creating recurring transfer") print(f"[EPOCH: {current_epoch}] creating recurring transfer")
@@ -84,9 +107,42 @@ def setup_teams_and_games(vega: VegaServiceNull):
factor=1.0, factor=1.0,
start_epoch=game_start, start_epoch=game_start,
end_epoch=game_end, end_epoch=game_end,
window_length=10 window_length=15,
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
vega.recurring_transfer(
from_key_name=PARTY_B.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
asset=tDAI_asset_id,
reference="reward",
asset_for_metric=tDAI_asset_id,
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
entity_scope=vega_protos.vega.ENTITY_SCOPE_INDIVIDUALS,
individual_scope=vega_protos.vega.INDIVIDUAL_SCOPE_IN_TEAM,
n_top_performers=1,
amount=100,
factor=1.0,
window_length=15
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
vega.recurring_transfer(
from_key_name=PARTY_C.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
asset=tDAI_asset_id,
reference="reward",
asset_for_metric=tDAI_asset_id,
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
entity_scope=vega_protos.vega.ENTITY_SCOPE_INDIVIDUALS,
individual_scope=vega_protos.vega.INDIVIDUAL_SCOPE_NOT_IN_TEAM,
n_top_performers=1,
amount=100,
factor=1.0,
window_length=15
) )
next_epoch(vega) next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] starting order activity") print(f"[EPOCH: {vega.statistics().epoch_seq}] starting order activity")
@@ -113,6 +169,22 @@ def setup_teams_and_games(vega: VegaServiceNull):
side="SIDE_BUY", side="SIDE_BUY",
volume=1, volume=1,
) )
vega.submit_order(
trading_key="Key 1",
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key="market_maker",
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
next_epoch(vega) next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] {i} epoch passed") print(f"[EPOCH: {vega.statistics().epoch_seq}] {i} epoch passed")
@@ -120,6 +192,7 @@ def setup_teams_and_games(vega: VegaServiceNull):
"market_id": tDAI_market, "market_id": tDAI_market,
"asset_id": tDAI_asset_id, "asset_id": tDAI_asset_id,
"team_id": team_id, "team_id": team_id,
"team_id_2": team_id_2,
"team_name": team_name, "team_name": team_name,
} }
@@ -136,65 +209,103 @@ def create_team(vega: VegaServiceNull):
return team_name return team_name
def test_team_page_games_table(team_page: Page): def test_team_page_games_table(team_page: Page):
team_page.get_by_test_id("games-toggle").click() team_page.get_by_test_id("games-toggle").click()
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)") expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)")
expect(team_page.get_by_test_id("rank-0")).to_have_text("1") expect(team_page.get_by_test_id("rank-0")).to_have_text("2")
expect(team_page.get_by_test_id("epoch-0")).to_have_text("18") expect(team_page.get_by_test_id("epoch-0")).to_have_text("19")
expect(team_page.get_by_test_id("type-0")).to_have_text("Price maker fees paid") expect(team_page.get_by_test_id("type-0")).to_have_text("Price maker fees paid")
expect(team_page.get_by_test_id("amount-0")).to_have_text("100,000,000") expect(team_page.get_by_test_id("amount-0")).to_have_text("74")
expect(team_page.get_by_test_id("participatingTeams-0")).to_have_text( expect(team_page.get_by_test_id("participatingTeams-0")).to_have_text("2")
"1" expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("4")
)
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text(
"2"
)
def test_team_page_members_table(team_page: Page): def test_team_page_members_table(team_page: Page):
team_page.get_by_test_id("members-toggle").click() team_page.get_by_test_id("members-toggle").click()
expect(team_page.get_by_test_id("members-toggle")).to_have_text("Members (3)") expect(team_page.get_by_test_id("members-toggle")).to_have_text("Members (4)")
expect(team_page.get_by_test_id("referee-0")).to_be_visible() expect(team_page.get_by_test_id("referee-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAt-0")).to_be_visible() expect(team_page.get_by_test_id("joinedAt-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAtEpoch-0")).to_have_text("8") expect(team_page.get_by_test_id("joinedAtEpoch-0")).to_have_text("9")
def test_team_page_headline(team_page: Page, setup_teams_and_games
): def test_team_page_headline(team_page: Page, setup_teams_and_games):
team_name = setup_teams_and_games["team_name"] team_name = setup_teams_and_games["team_name"]
expect(team_page.get_by_test_id("team-name")).to_have_text(team_name) expect(team_page.get_by_test_id("team-name")).to_have_text(team_name)
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("3") expect(team_page.get_by_test_id("members-count-stat")).to_have_text("4")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text( expect(team_page.get_by_test_id("total-games-stat")).to_have_text("2")
"1"
)
# TODO this still seems wrong as its always 0 # TODO this still seems wrong as its always 0
expect(team_page.get_by_test_id("total-volume-stat")).to_have_text( expect(team_page.get_by_test_id("total-volume-stat")).to_have_text("0")
"0"
)
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text( expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("214")
"100m"
)
def test_switch_teams(team_page: Page, vega: VegaServiceNull):
team_page.get_by_test_id("switch-team-button").click()
team_page.get_by_test_id("confirm-switch-button").click()
expect(team_page.get_by_test_id("dialog-content").first).to_be_visible()
vega.wait_fn(1)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
team_page.reload()
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("5")
@pytest.fixture(scope="module")
def competitions_page(vega, browser, request):
with init_page(vega, browser, request) as page:
risk_accepted_setup(page)
auth_setup(vega, page)
yield page
def test_leaderboard(competitions_page: Page, setup_teams_and_games): def test_leaderboard(competitions_page: Page, setup_teams_and_games):
team_name = setup_teams_and_games["team_name"] team_name = setup_teams_and_games["team_name"]
competitions_page.goto(f"/#/competitions/") competitions_page.reload()
expect(competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")).to_have_count(1) expect(
expect(competitions_page.get_by_test_id("team-0")).to_have_text(team_name) competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")
expect(competitions_page.get_by_test_id("status-0")).to_have_text("Open") ).to_have_count(1)
expect(
competitions_page.get_by_test_id("rank-1").locator(".text-vega-clight-500")
).to_have_count(1)
expect(competitions_page.get_by_test_id("team-1")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("status-1")).to_have_text("Open")
expect(competitions_page.get_by_test_id("earned-0")).to_have_text("100,000,000") expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160")
expect(competitions_page.get_by_test_id("games-0")).to_have_text("1") expect(competitions_page.get_by_test_id("games-1")).to_have_text("2")
# TODO still odd that this is 0 # TODO still odd that this is 0
expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-") expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-")
#TODO def test_games(competitions_page: Page):
#TODO currently no games appear which i think is a bug def test_game_card(competitions_page: Page):
expect(competitions_page.get_by_test_id("active-rewards-card")).to_have_count(2)
game_1 = competitions_page.get_by_test_id("active-rewards-card").first
expect(game_1).to_be_visible()
expect(game_1.get_by_test_id("entity-scope")).to_have_text("Individual")
expect(game_1.get_by_test_id("locked-for")).to_have_text("1 epoch")
expect(game_1.get_by_test_id("reward-value")).to_have_text("100.00")
expect(game_1.get_by_test_id("distribution-strategy")).to_have_text("Pro rata")
expect(game_1.get_by_test_id("dispatch-metric-info")).to_have_text("Price maker fees paid • ")
expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs")
expect(game_1.get_by_test_id("scope")).to_have_text("In team")
expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00")
expect(game_1.get_by_test_id("average-position")).to_have_text("0.00")
def test_create_team(competitions_page: Page, vega: VegaServiceNull):
change_keys(competitions_page, vega, "market_maker_2")
competitions_page.get_by_test_id("create-public-team-button").click()
competitions_page.get_by_test_id("team-name-input").fill("e2e")
competitions_page.get_by_test_id("team-url-input").fill("https://vega.xyz")
competitions_page.get_by_test_id("avatar-url-input").fill(
"http://placekitten.com/200/200"
)
competitions_page.get_by_test_id("team-form-submit-button").click()
expect(competitions_page.get_by_test_id("team-form-submit-button")).to_have_text(
"Confirming transaction..."
)
vega.wait_fn(2)
vega.wait_for_total_catchup()
expect(
competitions_page.get_by_test_id("team-creation-success-message")
).to_be_visible()
expect(competitions_page.get_by_test_id("team-id-display")).to_be_visible()
expect(competitions_page.get_by_test_id("team-id-display")).to_be_visible()
competitions_page.get_by_test_id("view-team-button").click()
expect(competitions_page.get_by_test_id("team-name")).to_have_text("e2e")
+18 -1
View File
@@ -17,7 +17,7 @@ fragment TeamStatsFields on TeamStatistics {
totalGamesPlayed totalGamesPlayed
quantumRewards { quantumRewards {
epoch epoch
total_quantum_rewards totalQuantumRewards
} }
gamesPlayed gamesPlayed
} }
@@ -51,6 +51,13 @@ fragment TeamGameFields on Game {
} }
} }
fragment TeamMemberStatsFields on TeamMemberStatistics {
partyId
totalQuantumVolume
totalQuantumRewards
totalGamesPlayed
}
query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) { query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
teams(teamId: $teamId) { teams(teamId: $teamId) {
edges { edges {
@@ -87,4 +94,14 @@ query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
} }
} }
} }
teamMembersStatistics(
teamId: $teamId
aggregationEpochs: $aggregationEpochs
) {
edges {
node {
...TeamMemberStatsFields
}
}
}
} }
+22 -4
View File
@@ -5,7 +5,7 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type TeamFieldsFragment = { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> }; export type TeamFieldsFragment = { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> };
export type TeamStatsFieldsFragment = { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, total_quantum_rewards: string }> }; export type TeamStatsFieldsFragment = { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> };
export type TeamRefereeFieldsFragment = { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number }; export type TeamRefereeFieldsFragment = { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number };
@@ -13,6 +13,8 @@ export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number,
export type TeamGameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> }; export type TeamGameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> };
export type TeamMemberStatsFieldsFragment = { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number };
export type TeamQueryVariables = Types.Exact<{ export type TeamQueryVariables = Types.Exact<{
teamId: Types.Scalars['ID']; teamId: Types.Scalars['ID'];
partyId?: Types.InputMaybe<Types.Scalars['ID']>; partyId?: Types.InputMaybe<Types.Scalars['ID']>;
@@ -20,7 +22,7 @@ export type TeamQueryVariables = Types.Exact<{
}>; }>;
export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, total_quantum_rewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> } } | null> | null } }; export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> } } | null> | null }, teamMembersStatistics?: { __typename?: 'TeamMembersStatisticsConnection', edges: Array<{ __typename?: 'TeamMemberStatisticsEdge', node: { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number } }> } | null };
export const TeamFieldsFragmentDoc = gql` export const TeamFieldsFragmentDoc = gql`
fragment TeamFields on Team { fragment TeamFields on Team {
@@ -43,7 +45,7 @@ export const TeamStatsFieldsFragmentDoc = gql`
totalGamesPlayed totalGamesPlayed
quantumRewards { quantumRewards {
epoch epoch
total_quantum_rewards totalQuantumRewards
} }
gamesPlayed gamesPlayed
} }
@@ -80,6 +82,14 @@ export const TeamGameFieldsFragmentDoc = gql`
} }
} }
${TeamEntityFragmentDoc}`; ${TeamEntityFragmentDoc}`;
export const TeamMemberStatsFieldsFragmentDoc = gql`
fragment TeamMemberStatsFields on TeamMemberStatistics {
partyId
totalQuantumVolume
totalQuantumRewards
totalGamesPlayed
}
`;
export const TeamDocument = gql` export const TeamDocument = gql`
query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) { query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
teams(teamId: $teamId) { teams(teamId: $teamId) {
@@ -117,11 +127,19 @@ export const TeamDocument = gql`
} }
} }
} }
teamMembersStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) {
edges {
node {
...TeamMemberStatsFields
}
}
}
} }
${TeamFieldsFragmentDoc} ${TeamFieldsFragmentDoc}
${TeamStatsFieldsFragmentDoc} ${TeamStatsFieldsFragmentDoc}
${TeamRefereeFieldsFragmentDoc} ${TeamRefereeFieldsFragmentDoc}
${TeamGameFieldsFragmentDoc}`; ${TeamGameFieldsFragmentDoc}
${TeamMemberStatsFieldsFragmentDoc}`;
/** /**
* __useTeamQuery__ * __useTeamQuery__
+14 -3
View File
@@ -1,12 +1,23 @@
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards'; import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
import { isActiveReward } from '../../components/rewards-container/active-rewards'; import { isActiveReward } from '../../components/rewards-container/active-rewards';
import { EntityScope, type TransferNode } from '@vegaprotocol/types'; import {
EntityScope,
IndividualScope,
type TransferNode,
} from '@vegaprotocol/types';
const isScopedToTeams = (node: TransferNode) => const isScopedToTeams = (node: TransferNode) =>
node.transfer.kind.__typename === 'RecurringTransfer' && node.transfer.kind.__typename === 'RecurringTransfer' &&
node.transfer.kind.dispatchStrategy?.entityScope === // scoped to teams
EntityScope.ENTITY_SCOPE_TEAMS; (node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_TEAMS ||
// or to individuals
(node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
// but they have to be in a team
node.transfer.kind.dispatchStrategy.individualScope ===
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM));
export const useGames = ({ export const useGames = ({
currentEpoch, currentEpoch,
+49 -10
View File
@@ -6,17 +6,24 @@ import {
type TeamStatsFieldsFragment, type TeamStatsFieldsFragment,
type TeamRefereeFieldsFragment, type TeamRefereeFieldsFragment,
type TeamEntityFragment, type TeamEntityFragment,
type TeamMemberStatsFieldsFragment,
} from './__generated__/Team'; } from './__generated__/Team';
import { DEFAULT_AGGREGATION_EPOCHS } from './use-teams'; import { DEFAULT_AGGREGATION_EPOCHS } from './use-teams';
export type Team = TeamFieldsFragment; export type Team = TeamFieldsFragment;
export type TeamStats = TeamStatsFieldsFragment; export type TeamStats = TeamStatsFieldsFragment;
export type Member = TeamRefereeFieldsFragment; export type Member = TeamRefereeFieldsFragment & {
isCreator: boolean;
totalGamesPlayed: number;
totalQuantumVolume: string;
totalQuantumRewards: string;
};
export type TeamEntity = TeamEntityFragment; export type TeamEntity = TeamEntityFragment;
export type TeamGame = ReturnType<typeof useTeam>['games'][number]; export type TeamGame = ReturnType<typeof useTeam>['games'][number];
export type MemberStats = TeamMemberStatsFieldsFragment;
export const useTeam = (teamId?: string, partyId?: string) => { export const useTeam = (teamId?: string, partyId?: string) => {
const { data, loading, error, refetch } = useTeamQuery({ const queryResult = useTeamQuery({
variables: { variables: {
teamId: teamId || '', teamId: teamId || '',
partyId, partyId,
@@ -26,7 +33,11 @@ export const useTeam = (teamId?: string, partyId?: string) => {
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-and-network',
}); });
const { data } = queryResult;
const teamEdge = data?.teams?.edges.find((e) => e.node.teamId === teamId); const teamEdge = data?.teams?.edges.find((e) => e.node.teamId === teamId);
const team = teamEdge?.node;
const partyTeam = data?.partyTeams?.edges?.length const partyTeam = data?.partyTeams?.edges?.length
? data.partyTeams.edges[0].node ? data.partyTeams.edges[0].node
: undefined; : undefined;
@@ -34,9 +45,40 @@ export const useTeam = (teamId?: string, partyId?: string) => {
const teamStatsEdge = data?.teamsStatistics?.edges.find( const teamStatsEdge = data?.teamsStatistics?.edges.find(
(e) => e.node.teamId === teamId (e) => e.node.teamId === teamId
); );
const members = data?.teamReferees?.edges
.filter((e) => e.node.teamId === teamId) const memberStats = data?.teamMembersStatistics?.edges.length
.map((e) => e.node); ? data.teamMembersStatistics.edges.map((e) => e.node)
: [];
const members: Member[] = data?.teamReferees?.edges.length
? data.teamReferees.edges
.filter((e) => e.node.teamId === teamId)
.map((e) => {
const member = e.node;
const stats = memberStats.find((m) => m.partyId === member.referee);
return {
...member,
isCreator: false,
totalQuantumVolume: stats ? stats.totalQuantumVolume : '0',
totalQuantumRewards: stats ? stats.totalQuantumRewards : '0',
totalGamesPlayed: stats ? stats.totalGamesPlayed : 0,
};
})
: [];
if (team) {
const ownerStats = memberStats.find((m) => m.partyId === team.referrer);
members.unshift({
teamId: team.teamId,
referee: team.referrer,
joinedAt: team?.createdAt,
joinedAtEpoch: team?.createdAtEpoch,
isCreator: true,
totalQuantumVolume: ownerStats ? ownerStats.totalQuantumVolume : '0',
totalQuantumRewards: ownerStats ? ownerStats.totalQuantumRewards : '0',
totalGamesPlayed: ownerStats ? ownerStats.totalGamesPlayed : 0,
});
}
// Find games where the current team participated in // Find games where the current team participated in
const gamesWithTeam = compact(data?.games.edges).map((edge) => { const gamesWithTeam = compact(data?.games.edges).map((edge) => {
@@ -60,12 +102,9 @@ export const useTeam = (teamId?: string, partyId?: string) => {
const games = orderBy(compact(gamesWithTeam), 'epoch', 'desc'); const games = orderBy(compact(gamesWithTeam), 'epoch', 'desc');
return { return {
data, ...queryResult,
loading,
error,
refetch,
stats: teamStatsEdge?.node, stats: teamStatsEdge?.node,
team: teamEdge?.node, team,
members, members,
games, games,
partyTeam, partyTeam,
+6 -32
View File
@@ -1,34 +1,12 @@
import orderBy from 'lodash/orderBy';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { type TeamsQuery, useTeamsQuery } from './__generated__/Teams'; import { useTeamsQuery } from './__generated__/Teams';
import { import { useTeamsStatisticsQuery } from './__generated__/TeamsStatistics';
type TeamsStatisticsQuery,
useTeamsStatisticsQuery,
} from './__generated__/TeamsStatistics';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import sortBy from 'lodash/sortBy';
import { type ArrayElement } from 'type-fest/source/internal';
type SortableField = keyof Omit<
ArrayElement<NonNullable<TeamsQuery['teams']>['edges']>['node'] &
ArrayElement<
NonNullable<TeamsStatisticsQuery['teamsStatistics']>['edges']
>['node'],
'__typename'
>;
type UseTeamsArgs = {
aggregationEpochs?: number;
sortByField?: SortableField[];
order?: 'asc' | 'desc';
};
export const DEFAULT_AGGREGATION_EPOCHS = 10; export const DEFAULT_AGGREGATION_EPOCHS = 10;
export const useTeams = ({ export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS,
sortByField = ['createdAtEpoch'],
order = 'asc',
}: UseTeamsArgs) => {
const { const {
data: teamsData, data: teamsData,
loading: teamsLoading, loading: teamsLoading,
@@ -57,12 +35,8 @@ export const useTeams = ({
...stats.find((s) => s.teamId === t.teamId), ...stats.find((s) => s.teamId === t.teamId),
})); }));
const sorted = sortBy(data, sortByField); return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc');
if (order === 'desc') { }, [teams, stats]);
return sorted.reverse();
}
return sorted;
}, [teams, sortByField, order, stats]);
return { return {
data, data,
@@ -59,6 +59,9 @@ export const DealTicketMarginDetails = ({
variables: { marketId: market.id, partyId: partyId || '' }, variables: { marketId: market.id, partyId: partyId || '' },
skip: !partyId, skip: !partyId,
}); });
const isInIsolatedMode =
positionEstimate?.margin.bestCase.marginMode ===
Schema.MarginMode.MARGIN_MODE_ISOLATED_MARGIN;
const liquidationEstimate = positionEstimate?.liquidation; const liquidationEstimate = positionEstimate?.liquidation;
const marginEstimate = positionEstimate?.margin; const marginEstimate = positionEstimate?.margin;
const totalMarginAccountBalance = const totalMarginAccountBalance =
@@ -70,18 +73,25 @@ export const DealTicketMarginDetails = ({
const { decimals: assetDecimals, quantum } = asset; const { decimals: assetDecimals, quantum } = asset;
let marginRequiredBestCase: string | undefined = undefined; let marginRequiredBestCase: string | undefined = undefined;
let marginRequiredWorstCase: string | undefined = undefined; let marginRequiredWorstCase: string | undefined = undefined;
const marginEstimateBestCase =
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) +
BigInt(marginEstimate?.bestCase.orderMarginLevel ?? 0);
const marginEstimateWorstCase =
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) +
BigInt(marginEstimate?.worstCase.orderMarginLevel ?? 0);
if (marginEstimate) {
if (currentMargins) {
const currentMargin =
BigInt(currentMargins.initialLevel) +
BigInt(currentMargins.orderMarginLevel);
const collateralIncreaseEstimateBestCase = BigInt(
positionEstimate?.collateralIncreaseEstimate.bestCase ?? '0'
);
const collateralIncreaseEstimateWorstCase = BigInt(
positionEstimate?.collateralIncreaseEstimate.worstCase ?? '0'
);
const marginEstimateBestCase = isInIsolatedMode
? totalMarginAccountBalance + collateralIncreaseEstimateBestCase
: BigInt(marginEstimate?.bestCase.initialLevel ?? 0);
const marginEstimateWorstCase = isInIsolatedMode
? totalMarginAccountBalance + collateralIncreaseEstimateWorstCase
: BigInt(marginEstimate?.worstCase.initialLevel ?? 0);
if (isInIsolatedMode) {
marginRequiredBestCase = collateralIncreaseEstimateBestCase.toString();
marginRequiredWorstCase = collateralIncreaseEstimateWorstCase.toString();
} else if (marginEstimate) {
if (currentMargins) {
const currentMargin = BigInt(currentMargins.initialLevel);
marginRequiredBestCase = ( marginRequiredBestCase = (
marginEstimateBestCase - currentMargin marginEstimateBestCase - currentMargin
).toString(); ).toString();
+3
View File
@@ -63,6 +63,7 @@
"Create": "Create", "Create": "Create",
"Create a team": "Create a team", "Create a team": "Create a team",
"Create a simple referral code to enjoy the referrer commission outlined in the current referral program": "Create a simple referral code to enjoy the referrer commission outlined in the current referral program", "Create a simple referral code to enjoy the referrer commission outlined in the current referral program": "Create a simple referral code to enjoy the referrer commission outlined in the current referral program",
"Create solo team": "Create solo team",
"Make your referral code a Team to compete in Competitions with your friends, appear in leaderboards on the <0>Competitions Homepage</0>, and earn rewards": "Make your referral code a Team to compete in Competitions with your friends, appear in leaderboards on the <0>Competitions Homepage</0>, and earn rewards", "Make your referral code a Team to compete in Competitions with your friends, appear in leaderboards on the <0>Competitions Homepage</0>, and earn rewards": "Make your referral code a Team to compete in Competitions with your friends, appear in leaderboards on the <0>Competitions Homepage</0>, and earn rewards",
"Current tier": "Current tier", "Current tier": "Current tier",
"DISCLAIMER_P1": "Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets. The Vega Protocol is an implementation layer (layer one) protocol made of free, public, open-source or source-available software. Use of the Vega Protocol involves various risks, including but not limited to, losses while digital assets are supplied to the Vega Protocol and losses due to the fluctuation of prices of assets.", "DISCLAIMER_P1": "Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets. The Vega Protocol is an implementation layer (layer one) protocol made of free, public, open-source or source-available software. Use of the Vega Protocol involves various risks, including but not limited to, losses while digital assets are supplied to the Vega Protocol and losses due to the fluctuation of prices of assets.",
@@ -181,6 +182,7 @@
"Markets": "Markets", "Markets": "Markets",
"Members": "Members", "Members": "Members",
"Members ({{count}})": "Members ({{count}})", "Members ({{count}})": "Members ({{count}})",
"Member ID": "Member ID",
"Menu": "Menu", "Menu": "Menu",
"Metamask Snap <0>quick start</0>": "Metamask Snap <0>quick start</0>", "Metamask Snap <0>quick start</0>": "Metamask Snap <0>quick start</0>",
"Min. epochs": "Min. epochs", "Min. epochs": "Min. epochs",
@@ -363,6 +365,7 @@
"Type": "Type", "Type": "Type",
"Unknown": "Unknown", "Unknown": "Unknown",
"Unknown settlement date": "Unknown settlement date", "Unknown settlement date": "Unknown settlement date",
"Update team": "Update team",
"URL": "URL", "URL": "URL",
"Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team", "Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team",
"Vega chart": "Vega chart", "Vega chart": "Vega chart",
+1 -1
View File
@@ -4682,7 +4682,7 @@ export type QuantumRewardsPerEpoch = {
/** Epoch for which this information is valid. */ /** Epoch for which this information is valid. */
epoch: Scalars['Int']; epoch: Scalars['Int'];
/** Total of rewards accumulated over the epoch period expressed in quantum value. */ /** Total of rewards accumulated over the epoch period expressed in quantum value. */
total_quantum_rewards: Scalars['String']; totalQuantumRewards: Scalars['String'];
}; };
/** Queries allow a caller to read data and filter data via GraphQL. */ /** Queries allow a caller to read data and filter data via GraphQL. */