feat: extract market name for active reward cards

This commit is contained in:
Madalina Raicu
2023-12-18 19:18:33 +00:00
parent fd7374668f
commit 3a3d46ff63
6 changed files with 208 additions and 81 deletions
@@ -196,3 +196,18 @@ query VestingDetails($partyId: ID!) {
}
}
}
query MarketForRewards($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
}
}
}
}
@@ -50,6 +50,13 @@ export type VestingDetailsQueryVariables = Types.Exact<{
export type VestingDetailsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string, quantumBalance: string, epochSeq: number } | null } | null };
export type MarketForRewardsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type MarketForRewardsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null } } } } | null };
export const RewardsPageDocument = gql`
query RewardsPage($partyId: ID!) {
@@ -415,4 +422,48 @@ export function useVestingDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt
}
export type VestingDetailsQueryHookResult = ReturnType<typeof useVestingDetailsQuery>;
export type VestingDetailsLazyQueryHookResult = ReturnType<typeof useVestingDetailsLazyQuery>;
export type VestingDetailsQueryResult = Apollo.QueryResult<VestingDetailsQuery, VestingDetailsQueryVariables>;
export type VestingDetailsQueryResult = Apollo.QueryResult<VestingDetailsQuery, VestingDetailsQueryVariables>;
export const MarketForRewardsDocument = gql`
query MarketForRewards($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
}
}
}
}
`;
/**
* __useMarketForRewardsQuery__
*
* To run a query within a React component, call `useMarketForRewardsQuery` and pass it any options that fit your needs.
* When your component renders, `useMarketForRewardsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMarketForRewardsQuery({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useMarketForRewardsQuery(baseOptions: Apollo.QueryHookOptions<MarketForRewardsQuery, MarketForRewardsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MarketForRewardsQuery, MarketForRewardsQueryVariables>(MarketForRewardsDocument, options);
}
export function useMarketForRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketForRewardsQuery, MarketForRewardsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MarketForRewardsQuery, MarketForRewardsQueryVariables>(MarketForRewardsDocument, options);
}
export type MarketForRewardsQueryHookResult = ReturnType<typeof useMarketForRewardsQuery>;
export type MarketForRewardsLazyQueryHookResult = ReturnType<typeof useMarketForRewardsLazyQuery>;
export type MarketForRewardsQueryResult = Apollo.QueryResult<MarketForRewardsQuery, MarketForRewardsQueryVariables>;
@@ -1,6 +1,9 @@
import { useActiveRewardsQuery } from './__generated__/Rewards';
import {
useActiveRewardsQuery,
useMarketForRewardsQuery,
} from './__generated__/Rewards';
import { useT } from '../../lib/use-t';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { addDecimalsFormatNumber, formatNumber } from '@vegaprotocol/utils';
import classNames from 'classnames';
import {
Icon,
@@ -25,9 +28,39 @@ import {
DispatchMetric,
DispatchMetricDescription,
DispatchMetricLabels,
type RecurringTransfer,
} from '@vegaprotocol/types';
import { Card } from '../card/card';
import { useMemo } from 'react';
const isActiveReward = (node: TransferNode, currentEpoch: number) => {
const { transfer } = node;
if (transfer.kind.__typename !== 'RecurringTransfer') {
return false;
}
const { dispatchStrategy } = transfer.kind;
if (!dispatchStrategy) {
return false;
}
if (transfer.kind.endEpoch && transfer.kind.endEpoch < currentEpoch) {
return false;
}
if (transfer.status !== TransferStatus.STATUS_PENDING) {
return false;
}
if (node.transfer.reference !== 'reward') {
return false;
}
return true;
};
export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
const t = useT();
const { data: activeRewardsData } = useActiveRewardsQuery({
variables: {
isReward: true,
@@ -36,27 +69,42 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
const transfers = activeRewardsData?.transfersConnection?.edges
?.map((e) => e?.node as TransferNode)
.filter((node) => node.transfer.reference === 'reward');
.filter((node) => isActiveReward(node, currentEpoch));
if (!transfers) return null;
if (!transfers || !transfers.length) return null;
return (
<div className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(343px,_1fr))]">
{transfers.map((node, i) => {
return (
node && (
<ActiveRewardCard
key={i}
transferNode={node}
currentEpoch={currentEpoch}
/>
)
);
})}
</div>
<Card title={t('Active rewards')} className="lg:col-span-full">
<div className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(343px,_1fr))]">
{transfers.map((node, i) => {
const { transfer } = node;
if (!isActiveReward(node, currentEpoch)) {
return null;
}
if (transfer.kind.__typename !== 'RecurringTransfer') {
return null;
}
return (
node && (
<ActiveRewardCard
key={i}
transferNode={node}
kind={transfer.kind}
currentEpoch={currentEpoch}
/>
)
);
})}
</div>
</Card>
);
};
// This was built to be a status indicator for the rewards based on the transfer status
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StatusIndicator = ({
status,
reason,
@@ -114,26 +162,42 @@ const StatusIndicator = ({
export const ActiveRewardCard = ({
transferNode,
currentEpoch,
kind,
}: {
transferNode: TransferNode;
currentEpoch: number;
kind: RecurringTransfer;
}) => {
const t = useT();
const { transfer } = transferNode;
if (transfer.kind.__typename !== 'RecurringTransfer') {
return null;
}
const { dispatchStrategy } = transfer.kind;
const { dispatchStrategy } = kind;
const marketIds = dispatchStrategy?.marketIdsInScope;
const { data: marketNameData } = useMarketForRewardsQuery({
variables: {
marketId: marketIds ? marketIds[0] : '',
},
});
const marketName = useMemo(() => {
if (marketNameData && marketIds && marketIds.length > 1) {
return 'Specific markets';
} else if (
marketNameData &&
marketIds &&
marketNameData &&
marketIds.length === 1
) {
return marketNameData?.market?.tradableInstrument?.instrument?.name || '';
}
return '';
}, [marketIds, marketNameData]);
if (!dispatchStrategy) {
return null;
}
if (transfer.kind.endEpoch && transfer.kind.endEpoch < currentEpoch) {
return null;
}
const { gradientClassName, mainClassName } = getGradientClasses(
dispatchStrategy.dispatchMetric
);
@@ -170,7 +234,7 @@ export const ActiveRewardCard = ({
</div>
<div className="flex flex-col gap-2 items-center text-center">
<span className="flex flex-col gap-1 font-alpha calt text-2xl shrink-1 text-center">
<span className="flex flex-col gap-1 font-alpha liga text-2xl shrink-1 text-center">
<span>
{addDecimalsFormatNumber(
transferNode.transfer.amount,
@@ -210,40 +274,44 @@ export const ActiveRewardCard = ({
/>
<span className="text-muted text-xs whitespace-nowrap">
{t('{{lock}} epochs', {
lock: transfer.kind.dispatchStrategy?.lockPeriod,
lock: kind.dispatchStrategy?.lockPeriod,
})}
</span>
</div>
</div>
<span className="border-[0.5px] border-gray-700" />
{/* TODO use market symbol or market name */}
<span>
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '}
{transfer.asset?.symbol} {transfer.asset?.name}
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]}
{marketName && `${marketName}`}
</span>
<div className="flex items-center gap-8 flex-wrap">
{
{kind.endEpoch && (
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')}</span>
<span>
{t('{{epochs}} epochs', {
epochs: transfer.kind.endEpoch
? transfer.kind.endEpoch - currentEpoch
epochs: kind.endEpoch
? formatNumber(kind.endEpoch - currentEpoch)
: '-',
})}
</span>
</span>
}
)}
{
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span>
<span>
{t('{{epochs}} epochs', {
epochs: transfer.kind.dispatchStrategy?.windowLength,
})}
{dispatchStrategy.windowLength === 1
? t('{{epochs}} epoch', {
epochs: formatNumber(dispatchStrategy.windowLength),
})
: t('{{epochs}} epoch(s)', {
epochs: formatNumber(dispatchStrategy.windowLength),
})}
</span>
</span>
}
@@ -264,10 +332,10 @@ export const ActiveRewardCard = ({
</span>
<span className="flex items-center gap-1">
{transfer.kind.dispatchStrategy?.teamScope && (
{kind.dispatchStrategy?.teamScope && (
<Tooltip
description={
<span>{transfer.kind.dispatchStrategy?.teamScope}</span>
<span>{kind.dispatchStrategy?.teamScope}</span>
}
>
<span className="flex items-center p-1 rounded-full border border-gray-600">
@@ -275,12 +343,10 @@ export const ActiveRewardCard = ({
</span>
</Tooltip>
)}
{transfer.kind.dispatchStrategy?.individualScope && (
{kind.dispatchStrategy?.individualScope && (
<Tooltip
description={
<span>
{transfer.kind.dispatchStrategy?.individualScope}
</span>
<span>{kind.dispatchStrategy?.individualScope}</span>
}
>
<span className="flex items-center p-1 rounded-full border border-gray-600">
@@ -288,10 +354,11 @@ export const ActiveRewardCard = ({
</span>
</Tooltip>
)}
<StatusIndicator
{/* Shows transfer status */}
{/* <StatusIndicator
status={transfer.status}
reason={transfer.reason}
/>
/> */}
</span>
</span>
@@ -301,32 +368,24 @@ export const ActiveRewardCard = ({
</span>
<span className="flex items-center gap-1">
{addDecimalsFormatNumber(
transfer.kind.dispatchStrategy?.stakingRequirement || 0,
kind.dispatchStrategy?.stakingRequirement || 0,
transfer.asset?.decimals || 0
)}{' '}
{transfer.asset?.symbol}
{/* <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('Notional TWAP Requirement')}{' '}
{t('Notional TWAP')}{' '}
</span>
<span className="flex items-center gap-1">
{addDecimalsFormatNumber(
transfer.kind.dispatchStrategy
kind.dispatchStrategy
?.notionalTimeWeightedAveragePositionRequirement || 0,
transfer.asset?.decimals || 0
)}{' '}
{transfer.asset?.symbol}
{/* <StatusIndicator
status={transfer.status}
reason={transfer.reason}
/> */}
</span>
</span>
</div>
@@ -345,8 +404,8 @@ const getGradientClasses = (d: DispatchMetric | undefined) => {
};
case DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED:
return {
gradientClassName: 'from-vega-purple-500 to-vega-blue-400',
mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%',
gradientClassName: 'from-vega-green-500 to-vega-yellow-500',
mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_MAKER_FEES_PAID:
return {
@@ -354,11 +413,11 @@ const getGradientClasses = (d: DispatchMetric | undefined) => {
mainClassName: 'from-vega-orange-400 dark:from-vega-orange-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_MARKET_VALUE:
return {
gradientClassName: 'from-vega-green-500 to-vega-yellow-500',
mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_RELATIVE_RETURN:
return {
gradientClassName: 'from-vega-purple-500 to-vega-blue-400',
mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_RETURN_VOLATILITY:
return {
gradientClassName: 'from-vega-blue-500 to-vega-green-400',
@@ -367,8 +426,8 @@ const getGradientClasses = (d: DispatchMetric | undefined) => {
case DispatchMetric.DISPATCH_METRIC_VALIDATOR_RANKING:
default:
return {
gradientClassName: 'from-vega-purple-500 to-vega-blue-400',
mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%',
gradientClassName: 'from-vega-pink-500 to-vega-purple-400',
mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%',
};
}
};
@@ -254,7 +254,7 @@ export const RewardsContainer = () => {
);
})}
{pubKey && streaks && (
<Card title={t('Activity streak')} className="lg:col-span-full">
<Card title={t('Activity Streak')} className="lg:col-span-full">
<span className="flex flex-col mx-8">
{streaks.map((streak, i) => (
<ActivityStreak
@@ -279,9 +279,7 @@ export const RewardsContainer = () => {
</span>
</Card>
)}
<Card title={t('Active rewards')} className="lg:col-span-full">
<ActiveRewards currentEpoch={Number(epochData?.epoch.id)} />
</Card>
<ActiveRewards currentEpoch={Number(epochData?.epoch.id)} />
<Card
title={t('Rewards history')}
className="lg:col-span-full"
@@ -48,6 +48,8 @@ export const ActivityStreak = ({
return userTier;
};
if (!tiers || tiers.length === 0) return null;
const userTierIndex = getUserTier();
const safeProgress = (i: number) => {
@@ -62,6 +64,11 @@ export const ActivityStreak = ({
.toNumber();
};
const epochsStreak =
tiers[userTierIndex].minimum_activity_streak - streak.activeFor >= 0
? tiers[userTierIndex].minimum_activity_streak - streak.activeFor
: 0;
const progressBarHeight = 'h-10';
return (
@@ -179,13 +186,7 @@ export const ActivityStreak = ({
<span>
<span className="text-vega-pink-500">
{t('{{epochs}} epochs streak', {
epochs:
tiers[userTierIndex].minimum_activity_streak -
streak.activeFor >=
0
? tiers[userTierIndex].minimum_activity_streak -
streak.activeFor
: 0,
epochs: formatNumber(epochsStreak),
})}
</span>
&nbsp;{' '}
@@ -32,9 +32,12 @@ export const RewardHoarderBonus = ({
);
return userTier;
};
if (!tiers || tiers.length === 0) return null;
const userTierIndex = getUserTier() - 1;
// TODO: extract qUSD from the API
const qAsset = 'qUSD';
const safeProgress = (i: number) => {
if (i < userTierIndex) return 100;
if (i > userTierIndex) return 0;
@@ -71,9 +74,7 @@ export const RewardHoarderBonus = ({
})}
</span>
<span className="text-muted text-xs">
{t('{{epochs}} qUSD', {
epochs: formatNumber(tier.minimum_quantum_balance),
})}
{formatNumber(tier.minimum_quantum_balance)} {qAsset}
</span>
</span>
@@ -147,7 +148,9 @@ export const RewardHoarderBonus = ({
<div className="flex items-center gap-1">
<VegaIcon name={VegaIconNames.STREAK} />
<span>{vestingDetails.quantumBalance} qUSD</span>
<span>
{formatNumber(vestingDetails.quantumBalance)} {qAsset}
</span>
</div>
</div>
</>