Compare commits

...
34 changed files with 781 additions and 365 deletions
+1
View File
@@ -26,6 +26,7 @@ NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
# NX_DISABLE_CLOSE_POSITION=false
NX_TEAM_COMPETITION=true
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
+5
View File
@@ -28,3 +28,8 @@ NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
NX_TEAM_COMPETITION=true
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+1
View File
@@ -26,6 +26,7 @@ NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TEAM_COMPETITION=true
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+1
View File
@@ -26,6 +26,7 @@ NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TEAM_COMPETITION=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
@@ -1,28 +1,9 @@
import { useSearchParams } from 'react-router-dom';
import { Controller, useForm } from 'react-hook-form';
import {
Intent,
TextArea,
TradingAnchorButton,
TradingButton,
TradingCheckbox,
TradingFormGroup,
TradingInput,
TradingInputError,
} from '@vegaprotocol/ui-toolkit';
import {
useVegaWallet,
type CreateReferralSet,
type Status,
useVegaWalletDialogStore,
} from '@vegaprotocol/wallet';
import {
addDecimalsFormatNumber,
isValidVegaPublicKey,
URL_REGEX,
} from '@vegaprotocol/utils';
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { useT } from '../../lib/use-t';
import { useCreateReferralSet } from '../../lib/hooks/use-create-referral-set';
import { useReferralSetTransaction } from '../../lib/hooks/use-referral-set-transaction';
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
import { RainbowButton } from '../../components/rainbow-button';
import { usePageTitle } from '../../lib/hooks/use-page-title';
@@ -30,14 +11,7 @@ import { ErrorBoundary } from '../../components/error-boundary';
import { Box } from '../../components/competitions/box';
import { LayoutWithGradient } from '../../components/layouts-inner';
import { Links } from '../../lib/links';
interface FormFields {
name: string;
url: string;
avatarUrl: string;
private: boolean;
allowList: string;
}
import { TeamForm, TransactionType } from './team-form';
export const CompetitionsCreateTeam = () => {
const [searchParams] = useSearchParams();
@@ -85,7 +59,7 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
const createLink = useLinks(DApp.Governance);
const { err, status, code, isEligible, requiredStake, onSubmit } =
useCreateReferralSet({
useReferralSetTransaction({
onSuccess: (code) => {
// For some reason team creation takes a long time, too long even to make
// polling viable, so its not feasible to navigate to the team page
@@ -146,7 +120,8 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
}
return (
<CreateTeamForm
<TeamForm
type={TransactionType.CreateReferralSet}
onSubmit={onSubmit}
status={status}
err={err}
@@ -154,180 +129,3 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
/>
);
};
const CreateTeamForm = ({
status,
err,
isSolo,
onSubmit,
}: {
status: ReturnType<typeof useCreateReferralSet>['status'];
err: ReturnType<typeof useCreateReferralSet>['err'];
isSolo: boolean;
onSubmit: (tx: CreateReferralSet) => void;
}) => {
const t = useT();
const {
register,
handleSubmit,
control,
watch,
formState: { errors },
} = useForm<FormFields>({
defaultValues: {
private: isSolo,
},
});
const isPrivate = watch('private');
const createTeam = (fields: FormFields) => {
onSubmit({
createReferralSet: {
isTeam: true,
team: {
name: fields.name,
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
allowList: fields.private ? parseAllowListText(fields.allowList) : [],
},
},
});
};
return (
<form onSubmit={handleSubmit(createTeam)}>
<TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput {...register('name', { required: t('Required') })} />
{errors.name?.message && (
<TradingInputError forInput="name">
{errors.name.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('URL')}
labelFor="url"
labelDescription={t(
'Provide a link so users can learn more about your team'
)}
>
<TradingInput
{...register('url', {
pattern: { value: URL_REGEX, message: t('Invalid URL') },
})}
/>
{errors.url?.message && (
<TradingInputError forInput="url">
{errors.url.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('Avatar URL')}
labelFor="avatarUrl"
labelDescription={t('Provide a URL to a hosted image')}
>
<TradingInput
{...register('avatarUrl', {
pattern: {
value: URL_REGEX,
message: t('Invalid image URL'),
},
})}
/>
{errors.avatarUrl?.message && (
<TradingInputError forInput="avatarUrl">
{errors.avatarUrl.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('Make team private')}
labelFor="private"
hideLabel={true}
>
<Controller
name="private"
control={control}
render={({ field }) => {
return (
<TradingCheckbox
label={t('Make team private')}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
}}
disabled={isSolo}
/>
);
}}
/>
</TradingFormGroup>
{isPrivate && (
<TradingFormGroup
label={t('Public key allow list')}
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'),
disabled: isSolo,
validate: {
allowList: (value) => {
const publicKeys = parseAllowListText(value);
if (publicKeys.every((pk) => isValidVegaPublicKey(pk))) {
return true;
}
return t('Invalid public key found in allow list');
},
},
})}
/>
{errors.allowList?.message && (
<TradingInputError forInput="avatarUrl">
{errors.allowList.message}
</TradingInputError>
)}
</TradingFormGroup>
)}
{err && (
<p className="text-danger text-xs mb-4 first-letter:capitalize">
{err}
</p>
)}
<SubmitButton status={status} />
</form>
);
};
const SubmitButton = ({ status }: { status: Status }) => {
const t = useT();
const disabled = status === 'pending' || status === 'requested';
let text = t('Create');
if (status === 'requested') {
text = t('Confirm in wallet...');
} else if (status === 'pending') {
text = t('Confirming transaction...');
}
return (
<TradingButton type="submit" intent={Intent.Info} disabled={disabled}>
{text}
</TradingButton>
);
};
const parseAllowListText = (str: string) => {
return str
.split(',')
.map((v) => v.trim())
.filter(Boolean);
};
@@ -3,7 +3,7 @@ import { ErrorBoundary } from '@sentry/react';
import { CompetitionsHeader } from '../../components/competitions/competitions-header';
import { Intent, Loader, TradingButton } from '@vegaprotocol/ui-toolkit';
import { useGames } from './hooks/use-games';
import { useGames } from '../../lib/hooks/use-games';
import { useCurrentEpochInfoQuery } from '../referrals/hooks/__generated__/Epoch';
import { Link, useNavigate } from 'react-router-dom';
import { Links } from '../../lib/links';
@@ -13,7 +13,7 @@ import {
} from '../../components/competitions/competitions-cta';
import { GamesContainer } from '../../components/competitions/games-container';
import { CompetitionsLeaderboard } from '../../components/competitions/competitions-leaderboard';
import { useTeams } from './hooks/use-teams';
import { useTeams } from '../../lib/hooks/use-teams';
import take from 'lodash/take';
import { usePageTitle } from '../../lib/hooks/use-page-title';
@@ -22,6 +22,7 @@ import { ErrorBoundary } from '../../components/error-boundary';
import { LayoutWithGradient } from '../../components/layouts-inner';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { JoinTeam } from './join-team';
import { UpdateTeamButton } from './update-team-button';
export const CompetitionsTeam = () => {
const t = useT();
@@ -93,17 +94,31 @@ const TeamPage = ({
<header className="flex gap-3 lg:gap-4 pt-5 lg:pt-10">
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
<div className="flex flex-col items-start gap-1 lg:gap-3">
<h1 className="calt text-2xl lg:text-3xl xl:text-5xl">{team.name}</h1>
<h1
className="calt text-2xl lg:text-3xl xl:text-5xl"
data-testid="team-name"
>
{team.name}
</h1>
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} />
</div>
</header>
<TeamStats stats={stats} members={members} games={games} />
<section>
<div className="flex gap-4 lg:gap-8 mb-4 border-b border-default">
<ToggleButton active={showGames} onClick={() => setShowGames(true)}>
<ToggleButton
active={showGames}
onClick={() => setShowGames(true)}
data-testid="games-toggle"
>
{t('Games ({{count}})', { count: games ? games.length : 0 })}
</ToggleButton>
<ToggleButton active={!showGames} onClick={() => setShowGames(false)}>
<ToggleButton
active={!showGames}
onClick={() => setShowGames(false)}
data-testid="members-toggle"
>
{t('Members ({{count}})', {
count: members ? members.length : 0,
})}
@@ -129,16 +144,22 @@ const Games = ({ games }: { games?: TeamGame[] }) => {
{
name: 'epoch',
displayName: t('Epoch'),
headerClassName: 'hidden md:block',
className: 'hidden md:block',
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
{ name: 'type', displayName: t('Type') },
{ name: 'amount', displayName: t('Amount earned') },
{
name: 'teams',
name: 'participatingTeams',
displayName: t('No. of participating teams'),
headerClassName: 'hidden md:block',
className: 'hidden md:block',
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
{
name: 'participatingMembers',
displayName: t('No. of participating members'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
]}
data={games.map((game) => ({
@@ -146,7 +167,8 @@ const Games = ({ games }: { games?: TeamGame[] }) => {
epoch: game.epoch,
type: DispatchMetricLabels[game.team.rewardMetric as DispatchMetric],
amount: formatNumber(game.team.totalRewardsEarned),
teams: game.numberOfParticipants,
participatingTeams: game.entities.length,
participatingMembers: game.numberOfParticipants,
}))}
noCollapse={true}
/>
@@ -2,7 +2,7 @@ import { ErrorBoundary } from '@sentry/react';
import { CompetitionsHeader } from '../../components/competitions/competitions-header';
import { useRef, useState } from 'react';
import { useT } from '../../lib/use-t';
import { useTeams } from './hooks/use-teams';
import { useTeams } from '../../lib/hooks/use-teams';
import { CompetitionsLeaderboard } from '../../components/competitions/competitions-leaderboard';
import {
Input,
@@ -0,0 +1,106 @@
import { ErrorBoundary } from '../../components/error-boundary';
import { usePageTitle } from '../../lib/hooks/use-page-title';
import { Box } from '../../components/competitions/box';
import { useT } from '../../lib/use-t';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { RainbowButton } from '../../components/rainbow-button';
import { Link, Navigate, useParams } from 'react-router-dom';
import { Links } from '../../lib/links';
import { useReferralSetTransaction } from '../../lib/hooks/use-referral-set-transaction';
import { type FormFields, TeamForm, TransactionType } from './team-form';
import { useTeam } from '../../lib/hooks/use-team';
import { LayoutWithGradient } from '../../components/layouts-inner';
export const CompetitionsUpdateTeam = () => {
const t = useT();
usePageTitle([t('Competitions'), t('Update a team')]);
const { pubKey, isReadOnly } = useVegaWallet();
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { teamId } = useParams<{ teamId: string }>();
if (!teamId) {
return <Navigate to={Links.COMPETITIONS()} />;
}
return (
<ErrorBoundary feature="update-team">
<LayoutWithGradient>
<div className="mx-auto md:w-2/3 max-w-xl">
<Box className="flex flex-col gap-4">
<h1 className="calt text-2xl lg:text-3xl xl:text-5xl">
{t('Update a team')}
</h1>
{pubKey && !isReadOnly ? (
<UpdateTeamFormContainer teamId={teamId} pubKey={pubKey} />
) : (
<>
<p>{t('Connect to update the details of your team.')}</p>
<RainbowButton variant="border" onClick={openWalletDialog}>
{t('Connect wallet')}
</RainbowButton>
</>
)}
</Box>
</div>
</LayoutWithGradient>
</ErrorBoundary>
);
};
const UpdateTeamFormContainer = ({
teamId,
pubKey,
}: {
teamId: string;
pubKey: string;
}) => {
const t = useT();
const { team, loading, error } = useTeam(teamId, pubKey);
const { err, status, onSubmit } = useReferralSetTransaction({
onSuccess: () => {
// NOOP
},
});
if (loading) {
return <Loader size="small" />;
}
if (error) {
return (
<Splash className="gap-1">
<span>{t('Something went wrong.')}</span>
<Link to={Links.COMPETITIONS_TEAM(teamId)} className="underline">
{t("Go back to the team's profile")}
</Link>
</Splash>
);
}
const isMyTeam = team?.referrer === pubKey;
if (!isMyTeam) {
return <Navigate to={Links.COMPETITIONS_TEAM(teamId)} />;
}
const defaultValues: FormFields = {
id: team.teamId,
name: team.name,
url: team.teamUrl,
avatarUrl: team.avatarUrl,
private: team.closed,
allowList: team.allowList.join(','),
};
return (
<TeamForm
type={TransactionType.UpdateReferralSet}
status={status}
err={err}
isSolo={team.closed}
onSubmit={onSubmit}
defaultValues={defaultValues}
/>
);
};
@@ -0,0 +1,254 @@
import {
TradingFormGroup,
TradingInput,
TradingInputError,
TradingCheckbox,
TextArea,
TradingButton,
Intent,
} from '@vegaprotocol/ui-toolkit';
import { URL_REGEX, isValidVegaPublicKey } from '@vegaprotocol/utils';
import { type useReferralSetTransaction } from '../../lib/hooks/use-referral-set-transaction';
import { useT } from '../../lib/use-t';
import { useForm, Controller } from 'react-hook-form';
import type {
CreateReferralSet,
UpdateReferralSet,
Status,
} from '@vegaprotocol/wallet';
export type FormFields = {
id: string;
name: string;
url: string;
avatarUrl: string;
private: boolean;
allowList: string;
};
export enum TransactionType {
CreateReferralSet,
UpdateReferralSet,
}
const prepareTransaction = (
type: TransactionType,
fields: FormFields
): CreateReferralSet | UpdateReferralSet => {
switch (type) {
case TransactionType.CreateReferralSet:
return {
createReferralSet: {
isTeam: true,
team: {
name: fields.name,
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
allowList: fields.private
? parseAllowListText(fields.allowList)
: [],
},
},
};
case TransactionType.UpdateReferralSet:
return {
updateReferralSet: {
id: fields.id,
isTeam: true,
team: {
name: fields.name,
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
allowList: fields.private
? parseAllowListText(fields.allowList)
: [],
},
},
};
}
};
export const TeamForm = ({
type,
status,
err,
isSolo,
onSubmit,
defaultValues,
}: {
type: TransactionType;
status: ReturnType<typeof useReferralSetTransaction>['status'];
err: ReturnType<typeof useReferralSetTransaction>['err'];
isSolo: boolean;
onSubmit: ReturnType<typeof useReferralSetTransaction>['onSubmit'];
defaultValues?: FormFields;
}) => {
const t = useT();
const {
register,
handleSubmit,
control,
watch,
formState: { errors },
} = useForm<FormFields>({
defaultValues: {
private: isSolo,
...defaultValues,
},
});
const isPrivate = watch('private');
const sendTransaction = (fields: FormFields) => {
onSubmit(prepareTransaction(type, fields));
};
return (
<form onSubmit={handleSubmit(sendTransaction)}>
<input
type="hidden"
{...register('id', {
disabled: true,
})}
/>
<TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput {...register('name', { required: t('Required') })} />
{errors.name?.message && (
<TradingInputError forInput="name">
{errors.name.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('URL')}
labelFor="url"
labelDescription={t(
'Provide a link so users can learn more about your team'
)}
>
<TradingInput
{...register('url', {
pattern: { value: URL_REGEX, message: t('Invalid URL') },
})}
/>
{errors.url?.message && (
<TradingInputError forInput="url">
{errors.url.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('Avatar URL')}
labelFor="avatarUrl"
labelDescription={t('Provide a URL to a hosted image')}
>
<TradingInput
{...register('avatarUrl', {
pattern: {
value: URL_REGEX,
message: t('Invalid image URL'),
},
})}
/>
{errors.avatarUrl?.message && (
<TradingInputError forInput="avatarUrl">
{errors.avatarUrl.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('Make team private')}
labelFor="private"
hideLabel={true}
>
<Controller
name="private"
control={control}
render={({ field }) => {
return (
<TradingCheckbox
label={t('Make team private')}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
}}
disabled={isSolo}
/>
);
}}
/>
</TradingFormGroup>
{isPrivate && (
<TradingFormGroup
label={t('Public key allow list')}
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'),
disabled: isSolo,
validate: {
allowList: (value) => {
const publicKeys = parseAllowListText(value);
if (publicKeys.every((pk) => isValidVegaPublicKey(pk))) {
return true;
}
return t('Invalid public key found in allow list');
},
},
})}
/>
{errors.allowList?.message && (
<TradingInputError forInput="avatarUrl">
{errors.allowList.message}
</TradingInputError>
)}
</TradingFormGroup>
)}
{err && <p className="text-danger text-xs mb-4 capitalize">{err}</p>}
<SubmitButton type={type} status={status} />
</form>
);
};
const SubmitButton = ({
type,
status,
}: {
type?: TransactionType;
status: Status;
}) => {
const t = useT();
const disabled = status === 'pending' || status === 'requested';
let text = t('Create');
if (type === TransactionType.UpdateReferralSet) {
text = t('Update');
}
if (status === 'requested') {
text = t('Confirm in wallet...');
} else if (status === 'pending') {
text = t('Confirming transaction...');
}
return (
<TradingButton type="submit" intent={Intent.Info} disabled={disabled}>
{text}
</TradingButton>
);
};
const parseAllowListText = (str: string) => {
return str
.split(',')
.map((v) => v.trim())
.filter(Boolean);
};
@@ -0,0 +1,20 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import { type Team } from '../../lib/hooks/use-team';
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links } from '../../lib/links';
export const UpdateTeamButton = ({ team }: { team: Team }) => {
const { pubKey, isReadOnly } = useVegaWallet();
if (pubKey && !isReadOnly && pubKey === team.referrer) {
return (
<TradingAnchorButton
data-testid="update-team-button"
href={Links.COMPETITIONS_UPDATE_TEAM(team.teamId)}
intent={Intent.Info}
/>
);
}
return null;
};
@@ -21,7 +21,7 @@ import { useT } from '../../lib/use-t';
import { Link, Navigate, useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../lib/links';
import { useReferralProgram } from './hooks/use-referral-program';
import { useCreateReferralSet } from '../../lib/hooks/use-create-referral-set';
import { useReferralSetTransaction } from '../../lib/hooks/use-referral-set-transaction';
import { Trans } from 'react-i18next';
export const CreateCodeContainer = () => {
@@ -155,7 +155,7 @@ const CreateCodeDialog = ({
stakeAvailable: currentStakeAvailable,
requiredStake,
onSubmit,
} = useCreateReferralSet();
} = useReferralSetTransaction();
const { details: programDetails } = useReferralProgram();
@@ -1,7 +1,7 @@
import { Link } from 'react-router-dom';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { getNumberFormat } from '@vegaprotocol/utils';
import { type useTeams } from '../../client-pages/competitions/hooks/use-teams';
import { type useTeams } from '../../lib/hooks/use-teams';
import { useT } from '../../lib/use-t';
import { Table } from '../table';
import { Rank } from './graphics/rank';
@@ -30,35 +30,42 @@ export const TeamStats = ({
<>
<StatSection>
<StatList>
<Stat value={members ? members.length : 0} label={t('Members')} />
<Stat
value={members ? members.length : 0}
label={t('Members')}
valueTestId="members-count-stat"
/>
<Stat
value={stats ? stats.totalGamesPlayed : 0}
label={t('Total games')}
tooltip={t('Total number of games this team has participated in')}
valueTestId="total-games-stat"
/>
<StatSectionSeparator />
<Stat
value={
stats
? formatNumberRounded(
new BigNumber(stats.totalQuantumVolume),
new BigNumber(stats.totalQuantumVolume || 0),
'1e3'
)
: 0
}
label={t('Total volume')}
valueTestId="total-volume-stat"
/>
<Stat
value={
stats
? formatNumberRounded(
new BigNumber(stats.totalQuantumRewards),
new BigNumber(stats.totalQuantumRewards || 0),
'1e3'
)
: 0
}
label={t('Rewards paid')}
label={t('Rewards paid out')}
tooltip={'Total amount of rewards paid out to this team in qUSD'}
valueTestId="rewards-paid-stat"
/>
</StatList>
</StatSection>
@@ -159,14 +166,18 @@ const Stat = ({
value,
label,
tooltip,
valueTestId,
}: {
value: ReactNode;
label: ReactNode;
tooltip?: string;
valueTestId?: string;
}) => {
return (
<div>
<dd className="text-3xl lg:text-4xl">{value}</dd>
<dd className="text-3xl lg:text-4xl" data-testid={valueTestId}>
{value}
</dd>
<dt className="text-sm text-muted">
{tooltip ? (
<Tooltip description={tooltip} underline={false}>
@@ -12,6 +12,7 @@ import {
VegaIconNames,
TradingInput,
TinyScroll,
truncateMiddle,
} from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons';
import {
@@ -30,6 +31,9 @@ import {
DispatchMetricLabels,
EntityScopeLabelMapping,
MarketState,
type DispatchStrategy,
IndividualScopeMapping,
IndividualScopeDescriptionMapping,
} from '@vegaprotocol/types';
import { Card } from '../card/card';
import { useMemo, useState } from 'react';
@@ -308,6 +312,9 @@ export const ActiveRewardCard = ({
MarketState.STATE_CLOSED,
].includes(m.state)
);
if (marketSettled) {
return null;
}
const assetInSettledMarket =
allMarkets &&
@@ -326,10 +333,6 @@ export const ActiveRewardCard = ({
return false;
});
if (marketSettled) {
return null;
}
// Gray out the cards that are related to suspended markets
const suspended = transferNode.markets?.some(
(m) =>
@@ -359,6 +362,7 @@ export const ActiveRewardCard = ({
: getGradientClasses(dispatchStrategy.dispatchMetric);
const entityScope = dispatchStrategy.entityScope;
return (
<div>
<div
@@ -474,83 +478,127 @@ export const ActiveRewardCard = ({
</span>
}
</div>
{dispatchStrategy?.dispatchMetric && (
<span className="text-muted text-sm h-[2rem]">
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
</span>
)}
<span className="border-[0.5px] border-gray-700" />
<div className="flex justify-between flex-wrap items-center gap-3 text-xs">
<span className="flex flex-col gap-1">
<span className="flex items-center gap-1 text-muted">
{t('Entity scope')}{' '}
</span>
<span className="flex items-center gap-1">
{kind.dispatchStrategy?.teamScope && (
<Tooltip
description={
<span>{kind.dispatchStrategy?.teamScope}</span>
}
>
<span className="flex items-center p-1 rounded-full border border-gray-600">
{<VegaIcon name={VegaIconNames.TEAM} size={16} />}
</span>
</Tooltip>
)}
{kind.dispatchStrategy?.individualScope && (
<Tooltip
description={
<span>{kind.dispatchStrategy?.individualScope}</span>
}
>
<span className="flex items-center p-1 rounded-full border border-gray-600">
{<VegaIcon name={VegaIconNames.MAN} size={16} />}
</span>
</Tooltip>
)}
{/* Shows transfer status */}
{/* <StatusIndicator
status={transfer.status}
reason={transfer.reason}
/> */}
</span>
</span>
<span className="flex flex-col gap-1">
<span className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}{' '}
</span>
<span className="flex items-center gap-1">
{addDecimalsFormatNumber(
kind.dispatchStrategy?.stakingRequirement || 0,
transfer.asset?.decimals || 0
)}
</span>
</span>
<span className="flex flex-col gap-1">
<span className="flex items-center gap-1 text-muted">
{t('Average position')}{' '}
</span>
<span className="flex items-center gap-1">
{addDecimalsFormatNumber(
kind.dispatchStrategy
?.notionalTimeWeightedAveragePositionRequirement || 0,
transfer.asset?.decimals || 0
)}
</span>
</span>
</div>
{kind.dispatchStrategy && (
<RewardRequirements
dispatchStrategy={kind.dispatchStrategy}
assetDecimalPlaces={transfer.asset?.decimals}
/>
)}
</div>
</div>
</div>
);
};
const RewardRequirements = ({
dispatchStrategy,
assetDecimalPlaces = 0,
}: {
dispatchStrategy: DispatchStrategy;
assetDecimalPlaces: number | undefined;
}) => {
const t = useT();
return (
<dl className="flex justify-between flex-wrap items-center gap-3 text-xs">
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('{{entity}} scope', {
entity: EntityScopeLabelMapping[dispatchStrategy.entityScope],
})}
</dt>
<dd className="flex items-center gap-1">
<RewardEntityScope dispatchStrategy={dispatchStrategy} />
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}
</dt>
<dd className="flex items-center gap-1">
{addDecimalsFormatNumber(
dispatchStrategy?.stakingRequirement || 0,
assetDecimalPlaces
)}
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Average position')}
</dt>
<dd className="flex items-center gap-1">
{addDecimalsFormatNumber(
dispatchStrategy?.notionalTimeWeightedAveragePositionRequirement ||
0,
assetDecimalPlaces
)}
</dd>
</div>
</dl>
);
};
const RewardEntityScope = ({
dispatchStrategy,
}: {
dispatchStrategy: DispatchStrategy;
}) => {
const t = useT();
if (dispatchStrategy.entityScope === EntityScope.ENTITY_SCOPE_TEAMS) {
return (
<Tooltip
description={
dispatchStrategy.teamScope?.length ? (
<div className="text-xs">
<p className="mb-1">{t('Eligible teams')}</p>
<ul>
{dispatchStrategy.teamScope.map((teamId) => {
if (!teamId) return null;
return <li key={teamId}>{truncateMiddle(teamId)}</li>;
})}
</ul>
</div>
) : (
t('All teams are eligible')
)
}
>
<span>
{dispatchStrategy.teamScope?.length
? t('Some teams')
: t('All teams')}
</span>
</Tooltip>
);
}
if (
dispatchStrategy.entityScope === EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
dispatchStrategy.individualScope
) {
return (
<Tooltip
description={
IndividualScopeDescriptionMapping[dispatchStrategy.individualScope]
}
>
<span>{IndividualScopeMapping[dispatchStrategy.individualScope]}</span>
</Tooltip>
);
}
return null;
};
const getGradientClasses = (d: DispatchMetric | undefined) => {
switch (d) {
case DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION:
+2 -2
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.74.0-preview.2
LOCAL_SERVER=false
VEGA_VERSION=v0.74.0-preview.7
LOCAL_SERVER=true
+120 -26
View File
@@ -5,6 +5,7 @@ from vega_sim.null_service import VegaServiceNull
from conftest import init_vega
from actions.utils import next_epoch
from fixtures.market import setup_continuous_market
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
@@ -13,7 +14,16 @@ def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def team_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/teams/{team_id}")
yield page
@pytest.fixture(scope="module")
def setup_teams_and_games(vega: VegaServiceNull):
tDAI_market = setup_continuous_market(vega)
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
@@ -50,6 +60,16 @@ def setup_teams_and_games(vega: VegaServiceNull):
vega.wait_fn(1)
vega.wait_for_total_catchup()
current_epoch = vega.statistics().epoch_seq
game_start = current_epoch + 1
game_end = current_epoch + 11
current_epoch = vega.statistics().epoch_seq
print(f"[EPOCH: {current_epoch}] creating recurring transfer")
print(f"Game start: {game_start}")
print(f"Game game end: {game_end}")
vega.recurring_transfer(
from_key_name=PARTY_A.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
@@ -62,39 +82,50 @@ def setup_teams_and_games(vega: VegaServiceNull):
n_top_performers=1,
amount=100,
factor=1.0,
start_epoch=game_start,
end_epoch=game_end,
window_length=10
)
vega.submit_order(
trading_key=PARTY_B.name,
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=PARTY_A.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
next_epoch(vega=vega)
return tDAI_market, tDAI_asset_id, team_id, team_name
next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] starting order activity")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_teams(vega: VegaServiceNull, page: Page):
market_id, asset_id, team_id, team_name = setup_teams_and_games(vega)
page.goto(f"/#/competitions/teams/{team_id}")
# page.pause()
expect(page.get_by_role("heading", level=1)).to_have_text(team_name)
expect(page.get_by_text("Members (3)")).to_be_visible()
# Team statistics will only return data when team has been active
# for DEFAULT_AGGREGATION_EPOCHS epochs
#
# https://vegaprotocol.slack.com/archives/C02KVKMAE82/p1706635625851769?thread_ts=1706631542.576449&cid=C02KVKMAE82
# Create trading activity for 10 epochs (which is the default)
for i in range(10):
vega.submit_order(
trading_key=PARTY_B.name,
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=PARTY_A.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] {i} epoch passed")
return {
"market_id": tDAI_market,
"asset_id": tDAI_asset_id,
"team_id": team_id,
"team_name": team_name,
}
def create_team(vega: VegaServiceNull):
team_name = "Foobar"
vega.create_referral_set(
key_name=PARTY_A.name,
name=team_name,
@@ -104,3 +135,66 @@ def create_team(vega: VegaServiceNull):
)
return team_name
def test_team_page_games_table(team_page: Page):
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("rank-0")).to_have_text("1")
expect(team_page.get_by_test_id("epoch-0")).to_have_text("18")
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("participatingTeams-0")).to_have_text(
"1"
)
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text(
"2"
)
def test_team_page_members_table(team_page: Page):
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("referee-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")
def test_team_page_headline(team_page: Page, setup_teams_and_games
):
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("members-count-stat")).to_have_text("3")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text(
"1"
)
# TODO this still seems wrong as its always 0
expect(team_page.get_by_test_id("total-volume-stat")).to_have_text(
"0"
)
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text(
"100m"
)
@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):
team_name = setup_teams_and_games["team_name"]
competitions_page.goto(f"/#/competitions/")
expect(competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")).to_have_count(1)
expect(competitions_page.get_by_test_id("team-0")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("status-0")).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("games-0")).to_have_text("1")
# TODO still odd that this is 0
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
+3 -2
View File
@@ -7,6 +7,7 @@ fragment TeamFields on Team {
createdAt
createdAtEpoch
closed
allowList
}
fragment TeamStatsFields on TeamStatistics {
@@ -50,7 +51,7 @@ fragment TeamGameFields on Game {
}
}
query Team($teamId: ID!, $partyId: ID) {
query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
teams(teamId: $teamId) {
edges {
node {
@@ -65,7 +66,7 @@ query Team($teamId: ID!, $partyId: ID) {
}
}
}
teamsStatistics(teamId: $teamId) {
teamsStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) {
edges {
node {
...TeamStatsFields
+9 -6
View File
@@ -3,23 +3,24 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
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 };
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 TeamRefereeFieldsFragment = { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number };
export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: string, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } };
export type TeamEntityFragment = { __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: string, 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 TeamQueryVariables = Types.Exact<{
teamId: Types.Scalars['ID'];
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
aggregationEpochs?: Types.InputMaybe<Types.Scalars['Int']>;
}>;
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 } }> } | 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 } }> } | 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: string, 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, 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 const TeamFieldsFragmentDoc = gql`
fragment TeamFields on Team {
@@ -31,6 +32,7 @@ export const TeamFieldsFragmentDoc = gql`
createdAt
createdAtEpoch
closed
allowList
}
`;
export const TeamStatsFieldsFragmentDoc = gql`
@@ -79,7 +81,7 @@ export const TeamGameFieldsFragmentDoc = gql`
}
${TeamEntityFragmentDoc}`;
export const TeamDocument = gql`
query Team($teamId: ID!, $partyId: ID) {
query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
teams(teamId: $teamId) {
edges {
node {
@@ -94,7 +96,7 @@ export const TeamDocument = gql`
}
}
}
teamsStatistics(teamId: $teamId) {
teamsStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) {
edges {
node {
...TeamStatsFields
@@ -135,6 +137,7 @@ ${TeamGameFieldsFragmentDoc}`;
* variables: {
* teamId: // value for 'teamId'
* partyId: // value for 'partyId'
* aggregationEpochs: // value for 'aggregationEpochs'
* },
* });
*/
@@ -1,6 +1,6 @@
import compact from 'lodash/compact';
import { useActiveRewardsQuery } from '../../../components/rewards-container/__generated__/Rewards';
import { isActiveReward } from '../../../components/rewards-container/active-rewards';
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
import { isActiveReward } from '../../components/rewards-container/active-rewards';
import { EntityScope, type TransferNode } from '@vegaprotocol/types';
const isScopedToTeams = (node: TransferNode) =>
@@ -25,9 +25,8 @@ export const useGames = ({
const games = compact(data?.transfersConnection?.edges?.map((n) => n?.node))
.map((n) => n as TransferNode)
.filter((node) => {
const recurring = node.transfer.kind.__typename !== 'RecurringTransfer';
const active = onlyActive ? isActiveReward(node, currentEpoch) : true;
return active && recurring && isScopedToTeams(node);
return active && isScopedToTeams(node);
});
return {
@@ -1,14 +1,15 @@
import {
useSimpleTransaction,
type CreateReferralSet,
type Options,
type CreateReferralSet,
type UpdateReferralSet,
} from '@vegaprotocol/wallet';
import { useStakeAvailable } from './use-stake-available';
/**
* Manages state for creating a referral set or team
*/
export const useCreateReferralSet = (opts?: Options) => {
export const useReferralSetTransaction = (opts?: Options) => {
const { stakeAvailable, requiredStake, isEligible } = useStakeAvailable();
const { status, result, error, send } = useSimpleTransaction({
@@ -16,7 +17,7 @@ export const useCreateReferralSet = (opts?: Options) => {
onError: opts?.onError,
});
const onSubmit = (tx: CreateReferralSet) => {
const onSubmit = (tx: CreateReferralSet | UpdateReferralSet) => {
send(tx);
};
+7 -1
View File
@@ -7,6 +7,7 @@ import {
type TeamRefereeFieldsFragment,
type TeamEntityFragment,
} from './__generated__/Team';
import { DEFAULT_AGGREGATION_EPOCHS } from './use-teams';
export type Team = TeamFieldsFragment;
export type TeamStats = TeamStatsFieldsFragment;
@@ -16,7 +17,11 @@ export type TeamGame = ReturnType<typeof useTeam>['games'][number];
export const useTeam = (teamId?: string, partyId?: string) => {
const { data, loading, error, refetch } = useTeamQuery({
variables: { teamId: teamId || '', partyId },
variables: {
teamId: teamId || '',
partyId,
aggregationEpochs: DEFAULT_AGGREGATION_EPOCHS,
},
skip: !teamId,
fetchPolicy: 'cache-and-network',
});
@@ -47,6 +52,7 @@ export const useTeam = (teamId?: string, partyId?: string) => {
id: edge.node.id,
epoch: edge.node.epoch,
numberOfParticipants: edge.node.numberOfParticipants,
entities: edge.node.entities,
team: team as TeamEntity, // TS can't infer that all the game entities are teams
};
});
@@ -22,7 +22,7 @@ type UseTeamsArgs = {
order?: 'asc' | 'desc';
};
const DEFAULT_AGGREGATION_EPOCHS = 10;
export const DEFAULT_AGGREGATION_EPOCHS = 10;
export const useTeams = ({
aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS,
+6 -3
View File
@@ -18,9 +18,10 @@ export const Routes = {
REFERRALS_CREATE_CODE: '/referrals/create-code',
COMPETITIONS: '/competitions',
COMPETITIONS_TEAMS: '/competitions/teams',
COMPETITIONS_TEAM: '/competitions/teams/:teamId',
COMPETITIONS_CREATE_TEAM: '/competitions/teams/create',
COMPETITIONS_CREATE_TEAM_SOLO: '/competitions/teams/create?solo=true',
COMPETITIONS_TEAM: '/competitions/teams/:teamId',
COMPETITIONS_UPDATE_TEAM: '/competitions/teams/:teamId/update',
FEES: '/fees',
REWARDS: '/rewards',
} as const;
@@ -47,10 +48,12 @@ export const Links: ConsoleLinks = {
REFERRALS_CREATE_CODE: () => Routes.REFERRALS_CREATE_CODE,
COMPETITIONS: () => Routes.COMPETITIONS,
COMPETITIONS_TEAMS: () => Routes.COMPETITIONS_TEAMS,
COMPETITIONS_CREATE_TEAM: () => Routes.COMPETITIONS_CREATE_TEAM,
COMPETITIONS_CREATE_TEAM_SOLO: () => Routes.COMPETITIONS_CREATE_TEAM_SOLO,
COMPETITIONS_TEAM: (teamId: string) =>
Routes.COMPETITIONS_TEAM.replace(':teamId', teamId),
COMPETITIONS_CREATE_TEAM: () => Routes.COMPETITIONS_CREATE_TEAM,
COMPETITIONS_CREATE_TEAM_SOLO: () => Routes.COMPETITIONS_CREATE_TEAM_SOLO,
COMPETITIONS_UPDATE_TEAM: (teamId: string) =>
Routes.COMPETITIONS_UPDATE_TEAM.replace(':teamId', teamId),
FEES: () => Routes.FEES,
REWARDS: () => Routes.REWARDS,
};
+7 -2
View File
@@ -32,6 +32,7 @@ import { CompetitionsHome } from '../client-pages/competitions/competitions-home
import { CompetitionsTeams } from '../client-pages/competitions/competitions-teams';
import { CompetitionsTeam } from '../client-pages/competitions/competitions-team';
import { CompetitionsCreateTeam } from '../client-pages/competitions/competitions-create-team';
import { CompetitionsUpdateTeam } from '../client-pages/competitions/competitions-update-team';
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
// Using dynamic imports is a workaround for this until pennant is published as ESM
@@ -112,13 +113,17 @@ export const useRouterConfig = (): RouteObject[] => {
],
},
// pages with blurred background
{
path: AppRoutes.COMPETITIONS_TEAM,
element: <CompetitionsTeam />,
},
{
path: AppRoutes.COMPETITIONS_CREATE_TEAM,
element: <CompetitionsCreateTeam />,
},
{
path: AppRoutes.COMPETITIONS_TEAM,
element: <CompetitionsTeam />,
path: AppRoutes.COMPETITIONS_UPDATE_TEAM,
element: <CompetitionsUpdateTeam />,
},
],
}
+6 -1
View File
@@ -12,6 +12,8 @@
"Active": "Active",
"Activity Streak": "Activity Streak",
"All": "All",
"All teams": "All teams",
"All teams are eligible": "All teams are eligible",
"Amount earned": "Amount earned",
"An unknown error occurred.": "An unknown error occurred.",
"Anonymous": "Anonymous",
@@ -85,9 +87,11 @@
"Docs": "Docs",
"Earn commission & stake rewards": "Earn commission & stake rewards",
"Earned by me": "Earned by me",
"Eligible teams": "Eligible teams",
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
"Ends in": "Ends in",
"Entity scope": "Entity scope",
"{{entity}} scope": "{{entity}} scope",
"Environment not configured": "Environment not configured",
"Epoch": "Epoch",
"epochs in referral set": "epochs in referral set",
@@ -209,6 +213,7 @@
"No third party has access to your funds.": "No third party has access to your funds.",
"No volume discount program active": "No volume discount program active",
"No withdrawals": "No withdrawals",
"No. of participating members": "No. of participating members",
"No. of participating teams": "No. of participating teams",
"Node: {{VEGA_URL}} is unsuitable": "Node: {{VEGA_URL}} is unsuitable",
"Non-custodial and pseudonymous": "Non-custodial and pseudonymous",
@@ -408,7 +413,7 @@
"myVolume_other": "My volume (last {{count}} epochs)",
"numberEpochs": "{{count}} epochs",
"numberEpochs_one": "{{count}} epoch",
"Rewards paid": "Rewards paid",
"Rewards paid out": "Rewards paid out",
"{{reward}}x": "{{reward}}x",
"userActive": "{{active}} trader: {{count}} epochs so far",
"volumeLastEpochs": "Volume (last {{count}} epochs)",
+12 -12
View File
@@ -1879,12 +1879,8 @@ export type LiquidityFeeSettings = {
/** Configuration of a market liquidity monitoring parameters */
export type LiquidityMonitoringParameters = {
__typename?: 'LiquidityMonitoringParameters';
/** Specifies by how many seconds an auction should be extended if leaving the auction were to trigger a liquidity auction */
auctionExtensionSecs: Scalars['Int'];
/** Specifies parameters related to target stake calculation */
targetStakeParameters: TargetStakeParameters;
/** Specifies the triggering ratio for entering liquidity auction */
triggeringRatio: Scalars['String'];
};
/** A special order type for liquidity providers */
@@ -4002,10 +3998,10 @@ export type Perpetual = {
fundingRateScalingFactor: Scalars['String'];
/** Upper bound for the funding-rate such that the funding-rate will never be higher than this value */
fundingRateUpperBound: Scalars['String'];
/** Optional configuration driving the index price calculation for perpetual product */
indexPriceConfig?: Maybe<CompositePriceConfiguration>;
/** Continuously compounded interest rate used in funding rate calculation, in the range [-1, 1] */
interestRate: Scalars['String'];
/** Optional configuration driving the internal composite price calculation for perpetual product */
internalCompositePriceConfig?: Maybe<CompositePriceConfiguration>;
/** Controls how much the upcoming funding payment liability contributes to party's margin, in the range [0, 1] */
marginFundingFactor: Scalars['String'];
/** Quote name of the instrument */
@@ -4023,14 +4019,14 @@ export type PerpetualData = {
fundingPayment?: Maybe<Scalars['String']>;
/** Percentage difference between the time-weighted average price of the external and internal data point. */
fundingRate?: Maybe<Scalars['String']>;
/** The index price used for external VWAP calculation */
indexPrice: Scalars['String'];
/** The methodology used to calculated index price for perps */
indexPriceType: CompositePriceType;
/** Internal composite price used as input to the internal VWAP */
internalCompositePrice: Scalars['String'];
/** The methodology used to calculated internal composite price for perpetual markets */
internalCompositePriceType: CompositePriceType;
/** Time-weighted average price calculated from data points for this period from the internal data source. */
internalTwap?: Maybe<Scalars['String']>;
/** RFC3339Nano time indicating the next time index price will be calculated for perps where applicable */
nextIndexPriceCalc: Scalars['String'];
/** RFC3339Nano time indicating the next time internal composite price will be calculated for perpetual markets, where applicable */
nextInternalCompositePriceCalc: Scalars['String'];
/** Funding period sequence number */
seqNum: Scalars['Int'];
/** Time at which the funding period started */
@@ -5507,6 +5503,8 @@ export type ReferralSet = {
id: Scalars['ID'];
/** Party that created the set. */
referrer: Scalars['ID'];
/** Current number of members in the referral set. */
totalMembers: Scalars['Int'];
/** Timestamp as RFC3339Nano when the referral set was updated. */
updatedAt: Scalars['Timestamp'];
};
@@ -6333,6 +6331,8 @@ export type Team = {
teamId: Scalars['ID'];
/** Link to the team's homepage. */
teamUrl: Scalars['String'];
/** Current number of members in the team. */
totalMembers: Scalars['Int'];
};
/** Connection type for retrieving cursor-based paginated team data */
+15
View File
@@ -3,6 +3,7 @@ import type {
EntityScope,
GovernanceTransferKind,
GovernanceTransferType,
IndividualScope,
PeggedReference,
ProposalChange,
TransferStatus,
@@ -700,6 +701,20 @@ export const EntityScopeLabelMapping: { [e in EntityScope]: string } = {
ENTITY_SCOPE_TEAMS: 'Team',
};
export const IndividualScopeMapping: { [e in IndividualScope]: string } = {
INDIVIDUAL_SCOPE_ALL: 'All',
INDIVIDUAL_SCOPE_IN_TEAM: 'In team',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Not in team',
};
export const IndividualScopeDescriptionMapping: {
[e in IndividualScope]: string;
} = {
INDIVIDUAL_SCOPE_ALL: 'All parties are eligble',
INDIVIDUAL_SCOPE_IN_TEAM: 'Parties in teams are eligible',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Only parties not in teams are eligible',
};
export enum DistributionStrategyMapping {
/** Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has */
DISTRIBUTION_STRATEGY_PRO_RATA = 'Pro rata',
@@ -3,12 +3,14 @@ import type { ReactNode } from 'react';
export interface SplashProps {
children: ReactNode;
className?: classNames.Argument;
}
export const Splash = ({ children }: SplashProps) => {
export const Splash = ({ children, className }: SplashProps) => {
const splashClasses = classNames(
'w-full h-full text-xs text-center text-gray-800 dark:text-gray-200',
'flex items-center justify-center'
'flex items-center justify-center',
className
);
return <div className={splashClasses}>{children}</div>;
};
+16 -1
View File
@@ -458,6 +458,20 @@ export type CreateReferralSet = {
};
};
export type UpdateReferralSet = {
updateReferralSet: {
id: string;
isTeam: boolean;
team?: {
name: string;
teamUrl?: string;
avatarUrl?: string;
closed: boolean;
allowList: string[];
};
};
};
export enum MarginMode {
MARGIN_MODE_CROSS_MARGIN = 1,
MARGIN_MODE_ISOLATED_MARGIN,
@@ -489,7 +503,8 @@ export type Transaction =
| LiquidityProvisionSubmission
| ApplyReferralCode
| JoinTeam
| CreateReferralSet;
| CreateReferralSet
| UpdateReferralSet;
export const isMarginModeUpdateTransaction = (
transaction: Transaction