fix(trading): referrer stats tiles (#5143)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Art 2023-10-31 02:12:59 +01:00 committed by GitHub
parent 0580e90171
commit 3072b7824f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 117 additions and 43 deletions

View File

@ -18,6 +18,7 @@ import { Routes } from '../../lib/links';
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
import { t } from '@vegaprotocol/i18n';
import { Statistics } from './referral-statistics';
import { useReferralProgram } from './hooks/use-referral-program';
const RELOAD_DELAY = 3000;
@ -32,6 +33,7 @@ const validateCode = (value: string) => {
};
export const ApplyCodeForm = () => {
const program = useReferralProgram();
const navigate = useNavigate();
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
@ -237,7 +239,7 @@ export const ApplyCodeForm = () => {
{previewData ? (
<div className="mt-10">
<h2 className="text-2xl mb-5">{t('You are joining')}</h2>
<Statistics data={previewData} as="referee" />
<Statistics data={previewData} program={program} as="referee" />
</div>
) : null}
</>

View File

@ -1,5 +1,5 @@
query Referees($code: ID!, $aggregationDays: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) {
query Referees($code: ID!, $aggregationEpochs: Int) {
referralSetReferees(id: $code, aggregationEpochs: $aggregationEpochs) {
edges {
node {
referralSetId

View File

@ -10,6 +10,7 @@ query ReferralSetStats($code: ID!, $epoch: Int) {
referralSetRunningNotionalTakerVolume
rewardsMultiplier
rewardsFactorMultiplier
referrerTakerVolume
}
}
}

View File

@ -5,7 +5,7 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type RefereesQueryVariables = Types.Exact<{
code: Types.Scalars['ID'];
aggregationDays?: Types.InputMaybe<Types.Scalars['Int']>;
aggregationEpochs?: Types.InputMaybe<Types.Scalars['Int']>;
}>;
@ -13,8 +13,8 @@ export type RefereesQuery = { __typename?: 'Query', referralSetReferees: { __typ
export const RefereesDocument = gql`
query Referees($code: ID!, $aggregationDays: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) {
query Referees($code: ID!, $aggregationEpochs: Int) {
referralSetReferees(id: $code, aggregationEpochs: $aggregationEpochs) {
edges {
node {
referralSetId
@ -42,7 +42,7 @@ export const RefereesDocument = gql`
* const { data, loading, error } = useRefereesQuery({
* variables: {
* code: // value for 'code'
* aggregationDays: // value for 'aggregationDays'
* aggregationEpochs: // value for 'aggregationEpochs'
* },
* });
*/

View File

@ -9,7 +9,7 @@ export type ReferralSetStatsQueryVariables = Types.Exact<{
}>;
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string } } | null> } };
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string, referrerTakerVolume: string } } | null> } };
export const ReferralSetStatsDocument = gql`
@ -25,6 +25,7 @@ export const ReferralSetStatsDocument = gql`
referralSetRunningNotionalTakerVolume
rewardsMultiplier
rewardsFactorMultiplier
referrerTakerVolume
}
}
}

View File

@ -5,14 +5,14 @@ import compact from 'lodash/compact';
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
import { useReferralSetsQuery } from './__generated__/ReferralSets';
const DEFAULT_AGGREGATION_DAYS = 30;
export const DEFAULT_AGGREGATION_DAYS = 30;
export type Role = 'referrer' | 'referee';
type UseReferralArgs = (
| { code: string }
| { pubKey: string | null; role: Role }
) & {
aggregationDays?: number;
aggregationEpochs?: number;
};
const prepareVariables = (
@ -70,9 +70,9 @@ export const useReferral = (args: UseReferralArgs) => {
} = useRefereesQuery({
variables: {
code: referralSet?.id as string,
aggregationDays:
args.aggregationDays != null
? args.aggregationDays
aggregationEpochs:
args.aggregationEpochs != null
? args.aggregationEpochs
: DEFAULT_AGGREGATION_DAYS,
},
skip: !referralSet?.id,

View File

@ -6,7 +6,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
import { DEFAULT_AGGREGATION_DAYS, useReferral } from './hooks/use-referral';
import { CreateCodeContainer } from './create-code-form';
import classNames from 'classnames';
import { Table } from './table';
@ -32,21 +32,25 @@ import maxBy from 'lodash/maxBy';
export const ReferralStatistics = () => {
const { pubKey } = useVegaWallet();
const program = useReferralProgram();
const { data: referee } = useReferral({
pubKey,
role: 'referee',
aggregationEpochs: program.details?.windowLength,
});
const { data: referrer } = useReferral({
pubKey,
role: 'referrer',
aggregationEpochs: program.details?.windowLength,
});
if (referee?.code) {
return <Statistics data={referee} as="referee" />;
return <Statistics data={referee} program={program} as="referee" />;
}
if (referrer?.code) {
return <Statistics data={referrer} as="referrer" />;
return <Statistics data={referrer} program={program} as="referrer" />;
}
return <CreateCodeContainer />;
@ -54,14 +58,16 @@ export const ReferralStatistics = () => {
export const Statistics = ({
data,
program,
as,
}: {
data: NonNullable<ReturnType<typeof useReferral>['data']>;
program: ReturnType<typeof useReferralProgram>;
as: 'referrer' | 'referee';
}) => {
const { benefitTiers, details } = program;
const { data: epochData } = useCurrentEpochInfoQuery();
const { stakeAvailable } = useStakeAvailable();
const { benefitTiers } = useReferralProgram();
const { data: statsData } = useReferralSetStatsQuery({
variables: {
code: data.code,
@ -72,6 +78,13 @@ export const Statistics = ({
const currentEpoch = Number(epochData?.epoch.id);
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
});
const stats =
statsData?.referralSetStats.edges &&
compact(removePaginationWrapper(statsData.referralSetStats.edges));
@ -87,10 +100,13 @@ export const Statistics = ({
const runningVolumeValue = statsAvailable
? Number(statsAvailable.referralSetRunningNotionalTakerVolume)
: 0;
const referrerVolumeValue = statsAvailable
? Number(statsAvailable.referrerTakerVolume)
: 0;
const multiplier = statsAvailable
? Number(statsAvailable.rewardsMultiplier)
: 1;
const finalCommissionValue = !isNaN(multiplier)
const finalCommissionValue = isNaN(multiplier)
? baseCommissionValue
: multiplier * baseCommissionValue;
@ -118,7 +134,13 @@ export const Statistics = ({
: 0;
const baseCommissionTile = (
<StatTile title={t('Base commission rate')}>
<StatTile
title={t('Base commission rate')}
description={t('(Combined set volume %s over last %s epochs)', [
compactNumFormat.format(runningVolumeValue),
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString(),
])}
>
{baseCommissionValue * 100}%
</StatTile>
);
@ -134,7 +156,16 @@ export const Statistics = ({
</StatTile>
);
const finalCommissionTile = (
<StatTile title={t('Final commission rate')}>
<StatTile
title={t('Final commission rate')}
description={
!isNaN(multiplier)
? `(${baseCommissionValue * 100}% ⨉ ${multiplier} = ${
finalCommissionValue * 100
}%)`
: undefined
}
>
{finalCommissionValue * 100}%
</StatTile>
);
@ -143,12 +174,21 @@ export const Statistics = ({
<StatTile title={t('Number of traders')}>{numberOfTradersValue}</StatTile>
);
const codeTile = <CodeTile code={data?.code} />;
const createdAtTile = (
<StatTile title={t('Created at')}>
<span className="text-3xl">
{getDateFormat().format(new Date(data.createdAt))}
</span>
const codeTile = (
<CodeTile
code={data?.code}
createdAt={getDateFormat().format(new Date(data.createdAt))}
/>
);
const referrerVolumeTile = (
<StatTile
title={t(
'My volume (last %s epochs)',
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString()
)}
>
{compactNumFormat.format(referrerVolumeValue)}
</StatTile>
);
@ -157,7 +197,10 @@ export const Statistics = ({
.reduce((all, r) => all.plus(r), new BigNumber(0));
const totalCommissionTile = (
<StatTile
title={t('Total commission (last 30 days)')}
title={t(
'Total commission (last %s epochs)',
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString()
)}
description={t('(qUSD)')}
>
{getNumberFormat(0).format(Number(totalCommissionValue))}
@ -174,20 +217,13 @@ export const Statistics = ({
<div className="grid grid-rows-1 gap-5 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
{codeTile}
{createdAtTile}
{referrerVolumeTile}
{numberOfTradersTile}
{totalCommissionTile}
</div>
</>
);
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
});
const currentBenefitTierTile = (
<StatTile title={t('Current tier')}>
{currentBenefitTierValue?.tier || 'None'}
@ -266,7 +302,7 @@ export const Statistics = ({
{/* Referees (only for referrer view) */}
{as === 'referrer' && data.referees.length > 0 && (
<div className="mt-20 mb-20">
<h2 className="text-2xl mb-5">{t('Referees')}</h2>
<h2 className="mb-5 text-2xl">{t('Referees')}</h2>
<div
className={classNames(
collapsed && [
@ -292,10 +328,23 @@ export const Statistics = ({
columns={[
{ name: 'party', displayName: t('Trader') },
{ name: 'joined', displayName: t('Date Joined') },
{ name: 'volume', displayName: t('Volume (last 30 days)') },
{
name: 'volume',
displayName: t(
'Volume (last %s epochs)',
(
details?.windowLength || DEFAULT_AGGREGATION_DAYS
).toString()
),
},
{
name: 'commission',
displayName: t('Commission earned (last 30 days)'),
displayName: t(
'Commission earned (last %s epochs)',
(
details?.windowLength || DEFAULT_AGGREGATION_DAYS
).toString()
),
},
]}
data={sortBy(

View File

@ -109,6 +109,7 @@ export const TiersContainer = () => {
<Loading variant="large" />
) : (
<TiersTable
windowLength={details?.windowLength}
data={benefitTiers.map((bt) => ({
...bt,
tierElement: (
@ -162,6 +163,7 @@ const StakingTiers = ({
const TiersTable = ({
data,
windowLength,
}: {
data: Array<{
tier: number;
@ -170,6 +172,7 @@ const TiersTable = ({
discount: string;
volume: string;
}>;
windowLength?: number;
}) => {
return (
<Table
@ -181,7 +184,15 @@ const TiersTable = ({
tooltip: t('A percentage of commission earned by the referrer'),
},
{ name: 'discount', displayName: t('Referrer trading discount') },
{ name: 'volume', displayName: t('Min. trading volume') },
{
name: 'volume',
displayName: t(
'Min. trading volume %s',
windowLength
? t('(last %s epochs)', windowLength.toString())
: undefined
),
},
{ name: 'epochs', displayName: t('Min. epochs') },
]}
data={data.map((d) => ({

View File

@ -7,6 +7,7 @@ import {
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import { Button } from './buttons';
import { t } from '@vegaprotocol/i18n';
export const Tile = ({
className,
@ -54,13 +55,18 @@ const FADE_OUT_STYLE = classNames(
export const CodeTile = ({
code,
createdAt,
className,
}: {
code: string;
createdAt?: string;
className?: string;
}) => {
return (
<StatTile title="Your referral code">
<StatTile
title={t('Your referral code')}
description={createdAt ? t('(Created at: %s)', createdAt) : undefined}
>
<div className="flex gap-2 items-center justify-between">
<Tooltip
description={
@ -82,7 +88,7 @@ export const CodeTile = ({
</Tooltip>
<CopyWithTooltip text={code}>
<Button className="text-sm no-underline !py-0 !px-0 h-fit !bg-transparent">
<span className="sr-only">Copy</span>
<span className="sr-only">{t('Copy')}</span>
<VegaIcon size={24} name={VegaIconNames.COPY} />
</Button>
</CopyWithTooltip>

View File

@ -813,7 +813,7 @@ export type DispatchStrategy = {
/** Minimum notional time-weighted averaged position required for a party to be considered eligible */
notionalTimeWeightedAveragePositionRequirement: Scalars['String'];
/** Ascending order list of start rank and corresponding share ratio */
rankTable?: Maybe<RankTable>;
rankTable?: Maybe<Array<Maybe<RankTable>>>;
/** Minimum number of governance tokens, e.g. VEGA, staked for a party to be considered eligible */
stakingRequirement: Scalars['String'];
/** The teams in scope for the reward, if the entity is teams */
@ -3624,6 +3624,8 @@ export type PartyVestingStats = {
__typename?: 'PartyVestingStats';
/** Epoch for which the statistics are valid */
epochSeq: Scalars['Int'];
/** The balance of the party, in quantum. */
quantumBalance: Scalars['String'];
/** The reward bonus multiplier */
rewardBonusMultiplier: Scalars['String'];
};
@ -4831,7 +4833,7 @@ export type QueryprotocolUpgradeProposalsArgs = {
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryreferralSetRefereesArgs = {
aggregationDays?: InputMaybe<Scalars['Int']>;
aggregationEpochs?: InputMaybe<Scalars['Int']>;
id?: InputMaybe<Scalars['ID']>;
pagination?: InputMaybe<Pagination>;
referee?: InputMaybe<Scalars['ID']>;
@ -5088,6 +5090,8 @@ export type ReferralSetStats = {
partyId: Scalars['ID'];
/** Running volume for the set based on the window length of the current referral program. */
referralSetRunningNotionalTakerVolume: Scalars['String'];
/** The referrer's taker volume */
referrerTakerVolume: Scalars['String'];
/** Reward factor applied to the party. */
rewardFactor: Scalars['String'];
/** The proportion of the referees taker fees to be rewarded to the referrer. */