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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type RefereesQueryVariables = Types.Exact<{ export type RefereesQueryVariables = Types.Exact<{
code: Types.Scalars['ID']; 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` export const RefereesDocument = gql`
query Referees($code: ID!, $aggregationDays: Int) { query Referees($code: ID!, $aggregationEpochs: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) { referralSetReferees(id: $code, aggregationEpochs: $aggregationEpochs) {
edges { edges {
node { node {
referralSetId referralSetId
@ -42,7 +42,7 @@ export const RefereesDocument = gql`
* const { data, loading, error } = useRefereesQuery({ * const { data, loading, error } = useRefereesQuery({
* variables: { * variables: {
* code: // value for 'code' * 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` export const ReferralSetStatsDocument = gql`
@ -25,6 +25,7 @@ export const ReferralSetStatsDocument = gql`
referralSetRunningNotionalTakerVolume referralSetRunningNotionalTakerVolume
rewardsMultiplier rewardsMultiplier
rewardsFactorMultiplier rewardsFactorMultiplier
referrerTakerVolume
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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