Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70fe023536 | ||
|
|
7d1f5e76ce | ||
|
|
345be81142 | ||
|
|
2221e9faca | ||
|
|
7c0139e107 | ||
|
|
a157ff1e05 | ||
|
|
2e9fa0c52e | ||
|
|
c0790c1e93 |
@@ -1,5 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Auto-format all files
|
||||
yarn nx format:write
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
# Lint all staged files - this brings more value as pre-commit
|
||||
# yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
yarn nx affected -t test --exclude trading
|
||||
# yarn nx affected -t test --exclude trading
|
||||
|
||||
+4
-8
@@ -3,17 +3,16 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
@@ -26,6 +25,3 @@ NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -22,10 +22,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=false
|
||||
NX_REFERRALS=true
|
||||
NX_DISABLE_CLOSE_POSITION=true
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
NX_DISABLE_CLOSE_POSITION=true
|
||||
|
||||
@@ -23,7 +23,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
|
||||
@@ -13,15 +13,40 @@ import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { RainbowButton } from './buttons';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useReferral } from './hooks/use-referral';
|
||||
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
|
||||
import { Statistics, useStats } from './referral-statistics';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ns, useT } from '../../lib/use-t';
|
||||
import { useFundsAvailable } from './hooks/use-funds-available';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const RELOAD_DELAY = 3000;
|
||||
|
||||
const SPAM_PROTECTION_ERR = 'SPAM_PROTECTION_ERR';
|
||||
const SpamProtectionErr = ({
|
||||
requiredFunds,
|
||||
}: {
|
||||
requiredFunds?: string | number | bigint;
|
||||
}) => {
|
||||
if (!requiredFunds) return null;
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
return (
|
||||
<Trans
|
||||
defaults="To protect the network from spam, you must have at least {{requiredFunds}} <0>qUSD</0> of any asset on the network to proceed."
|
||||
values={{
|
||||
requiredFunds,
|
||||
}}
|
||||
components={[<QUSDTooltip key="qusd" />]}
|
||||
ns={ns}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
||||
const number = +`0x${value}`;
|
||||
if (!value || value.length !== 64) {
|
||||
@@ -32,20 +57,23 @@ const validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const ApplyCodeFormContainer = () => {
|
||||
export const ApplyCodeFormContainer = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data: referee } = useReferral({ pubKey, role: 'referee' });
|
||||
const { data: referrer } = useReferral({ pubKey, role: 'referrer' });
|
||||
const isInReferralSet = useIsInReferralSet(pubKey);
|
||||
|
||||
// go to main page if the current pubkey is already a referrer or referee
|
||||
if (referee || referrer) {
|
||||
// Navigate to the index page when already in the referral set.
|
||||
if (isInReferralSet) {
|
||||
return <Navigate to={Routes.REFERRALS} />;
|
||||
}
|
||||
|
||||
return <ApplyCodeForm />;
|
||||
return <ApplyCodeForm onSuccess={onSuccess} />;
|
||||
};
|
||||
|
||||
export const ApplyCodeForm = () => {
|
||||
export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
const t = useT();
|
||||
const program = useReferralProgram();
|
||||
const navigate = useNavigate();
|
||||
@@ -54,10 +82,15 @@ export const ApplyCodeForm = () => {
|
||||
);
|
||||
|
||||
const [status, setStatus] = useState<
|
||||
'requested' | 'failed' | 'successful' | null
|
||||
'requested' | 'no-funds' | 'successful' | null
|
||||
>(null);
|
||||
const txHash = useRef<string | null>(null);
|
||||
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
|
||||
const { isEligible, requiredFunds } = useFundsAvailable();
|
||||
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((s) => s.setViews);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -73,6 +106,17 @@ export const ApplyCodeForm = () => {
|
||||
code: validateCode(codeField, t) ? codeField : undefined,
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates if a connected party can apply a code (min funds span protection)
|
||||
*/
|
||||
const validateFundsAvailable = useCallback(() => {
|
||||
if (requiredFunds && !isEligible) {
|
||||
const err = SPAM_PROTECTION_ERR;
|
||||
return err;
|
||||
}
|
||||
return true;
|
||||
}, [isEligible, requiredFunds]);
|
||||
|
||||
/**
|
||||
* Validates the set a user tries to apply to.
|
||||
*/
|
||||
@@ -96,6 +140,15 @@ export const ApplyCodeForm = () => {
|
||||
if (code) setValue('code', code);
|
||||
}, [params, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const err = validateFundsAvailable();
|
||||
if (err !== true) {
|
||||
setStatus('no-funds');
|
||||
} else {
|
||||
setStatus(null);
|
||||
}
|
||||
}, [isEligible, validateFundsAvailable]);
|
||||
|
||||
const onSubmit = ({ code }: FieldValues) => {
|
||||
if (isReadOnly || !pubKey || !code || code.length === 0) {
|
||||
return;
|
||||
@@ -167,10 +220,11 @@ export const ApplyCodeForm = () => {
|
||||
useEffect(() => {
|
||||
if (status === 'successful') {
|
||||
setTimeout(() => {
|
||||
if (onSuccess) onSuccess();
|
||||
navigate(Routes.REFERRALS);
|
||||
}, RELOAD_DELAY);
|
||||
}
|
||||
}, [navigate, status]);
|
||||
}, [navigate, onSuccess, status]);
|
||||
|
||||
// show "code applied" message when successfully applied
|
||||
if (status === 'successful') {
|
||||
@@ -207,6 +261,18 @@ export const ApplyCodeForm = () => {
|
||||
};
|
||||
}
|
||||
|
||||
if (status === 'no-funds') {
|
||||
return {
|
||||
disabled: false,
|
||||
children: t('Deposit funds'),
|
||||
type: 'button' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
|
||||
onClick: ((event) => {
|
||||
event.preventDefault();
|
||||
setViews({ type: ViewType.Deposit }, currentRouteId);
|
||||
}) as MouseEventHandler,
|
||||
};
|
||||
}
|
||||
|
||||
if (status === 'requested') {
|
||||
return {
|
||||
disabled: true,
|
||||
@@ -236,7 +302,9 @@ export const ApplyCodeForm = () => {
|
||||
{t('Apply a referral code')}
|
||||
</h3>
|
||||
<p className="mb-4 text-center text-base">
|
||||
{t('Enter a referral code to get trading discounts.')}
|
||||
{t(
|
||||
'Apply a referral code to access the discount benefits of the current program.'
|
||||
)}
|
||||
</p>
|
||||
<form
|
||||
className={classNames('flex w-full flex-col gap-4', {
|
||||
@@ -251,8 +319,10 @@ export const ApplyCodeForm = () => {
|
||||
{...register('code', {
|
||||
required: t('You have to provide a code to apply it.'),
|
||||
validate: (value) => {
|
||||
const err = validateCode(value, t);
|
||||
if (err !== true) return err;
|
||||
const codeErr = validateCode(value, t);
|
||||
if (codeErr !== true) return codeErr;
|
||||
const fundsErr = validateFundsAvailable();
|
||||
if (fundsErr !== true) return fundsErr;
|
||||
return validateSet();
|
||||
},
|
||||
})}
|
||||
@@ -262,10 +332,26 @@ export const ApplyCodeForm = () => {
|
||||
</label>
|
||||
<RainbowButton variant="border" {...getButtonProps()} />
|
||||
</form>
|
||||
{errors.code && (
|
||||
<InputError className="overflow-auto break-words">
|
||||
{errors.code.message?.toString()}
|
||||
{status === 'no-funds' ? (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
<span>
|
||||
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
|
||||
</span>
|
||||
</InputError>
|
||||
) : (
|
||||
errors.code && (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
{errors.code.message === SPAM_PROTECTION_ERR ? (
|
||||
<span>
|
||||
<SpamProtectionErr
|
||||
requiredFunds={requiredFunds?.toString()}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
errors.code.message?.toString()
|
||||
)}
|
||||
</InputError>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{validateCode(codeField, t) === true && previewLoading && !previewData ? (
|
||||
|
||||
@@ -6,6 +6,8 @@ export const SKY_BACKGROUND =
|
||||
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
|
||||
|
||||
// TODO: Update the links to use the correct referral related pages
|
||||
export const REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||
export const ABOUT_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||
export const REFERRAL_DOCS_LINK =
|
||||
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
|
||||
export const ABOUT_REFERRAL_DOCS_LINK =
|
||||
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
|
||||
export const DISCLAIMER_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||
|
||||
@@ -19,14 +19,22 @@ import {
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
|
||||
import { useStakeAvailable } from './hooks/use-stake-available';
|
||||
import {
|
||||
ABOUT_REFERRAL_DOCS_LINK,
|
||||
DISCLAIMER_REFERRAL_DOCS_LINK,
|
||||
} from './constants';
|
||||
import { useReferral } from './hooks/use-referral';
|
||||
import { ABOUT_REFERRAL_DOCS_LINK } from './constants';
|
||||
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
|
||||
export const CreateCodeContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const isInReferralSet = useIsInReferralSet(pubKey);
|
||||
|
||||
// Navigate to the index page when already in the referral set.
|
||||
if (isInReferralSet) {
|
||||
return <Navigate to={Routes.REFERRALS} />;
|
||||
}
|
||||
|
||||
return <CreateCodeForm />;
|
||||
};
|
||||
|
||||
@@ -48,7 +56,7 @@ export const CreateCodeForm = () => {
|
||||
</h3>
|
||||
<p className="mb-4 text-center text-base">
|
||||
{t(
|
||||
'Generate a referral code to share with your friends and start earning commission.'
|
||||
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
@@ -98,10 +106,7 @@ const CreateCodeDialog = ({
|
||||
const { stakeAvailable: currentStakeAvailable, requiredStake } =
|
||||
useStakeAvailable();
|
||||
|
||||
const { data: referralSets } = useReferral({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
});
|
||||
const { details: programDetails } = useReferralProgram();
|
||||
|
||||
const onSubmit = () => {
|
||||
if (isReadOnly || !pubKey) {
|
||||
@@ -201,7 +206,7 @@ const CreateCodeDialog = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!referralSets) {
|
||||
if (!programDetails) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
||||
@@ -237,7 +242,9 @@ const CreateCodeDialog = ({
|
||||
intent={Intent.Primary}
|
||||
onClick={() => onSubmit()}
|
||||
{...getButtonProps()}
|
||||
></TradingButton>
|
||||
>
|
||||
{t('Yes')}
|
||||
</TradingButton>
|
||||
{status === 'idle' && (
|
||||
<TradingButton
|
||||
fill={true}
|
||||
@@ -255,9 +262,6 @@ const CreateCodeDialog = ({
|
||||
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
||||
{t('About the referral program')}
|
||||
</ExternalLink>
|
||||
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
|
||||
{t('Disclaimer')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -268,7 +272,7 @@ const CreateCodeDialog = ({
|
||||
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
||||
<p>
|
||||
{t(
|
||||
'Generate a referral code to share with your friends and start earning commission.'
|
||||
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
@@ -299,9 +303,6 @@ const CreateCodeDialog = ({
|
||||
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
||||
{t('About the referral program')}
|
||||
</ExternalLink>
|
||||
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
|
||||
{t('Disclaimer')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ export const NotFound = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="pt-32">
|
||||
<LayoutWithSky className="pt-32">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
|
||||
@@ -75,6 +75,6 @@ export const NotFound = () => {
|
||||
{t('Go back and try again')}
|
||||
</RainbowButton>
|
||||
</p>
|
||||
</div>
|
||||
</LayoutWithSky>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
query FundsAvailable($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
balance
|
||||
asset {
|
||||
decimals
|
||||
symbol
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
networkParameter(key: "spam.protection.applyReferral.min.funds") {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type FundsAvailableQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FundsAvailableQuery = { __typename?: 'Query', party?: { __typename?: 'Party', accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, asset: { __typename?: 'Asset', decimals: number, symbol: string, id: string } } } | null> | null } | null } | null, networkParameter?: { __typename?: 'NetworkParameter', key: string, value: string } | null };
|
||||
|
||||
|
||||
export const FundsAvailableDocument = gql`
|
||||
query FundsAvailable($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
balance
|
||||
asset {
|
||||
decimals
|
||||
symbol
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
networkParameter(key: "spam.protection.applyReferral.min.funds") {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useFundsAvailableQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFundsAvailableQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFundsAvailableQuery` 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 } = useFundsAvailableQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFundsAvailableQuery(baseOptions: Apollo.QueryHookOptions<FundsAvailableQuery, FundsAvailableQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<FundsAvailableQuery, FundsAvailableQueryVariables>(FundsAvailableDocument, options);
|
||||
}
|
||||
export function useFundsAvailableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FundsAvailableQuery, FundsAvailableQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<FundsAvailableQuery, FundsAvailableQueryVariables>(FundsAvailableDocument, options);
|
||||
}
|
||||
export type FundsAvailableQueryHookResult = ReturnType<typeof useFundsAvailableQuery>;
|
||||
export type FundsAvailableLazyQueryHookResult = ReturnType<typeof useFundsAvailableLazyQuery>;
|
||||
export type FundsAvailableQueryResult = Apollo.QueryResult<FundsAvailableQuery, FundsAvailableQueryVariables>;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useFundsAvailableQuery } from './__generated__/FundsAvailable';
|
||||
import compact from 'lodash/compact';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
/**
|
||||
* Gets the funds for given public key and required min for
|
||||
* the referral program.
|
||||
*
|
||||
* (Uses currently connected public key if left empty)
|
||||
*/
|
||||
export const useFundsAvailable = (pubKey?: string) => {
|
||||
const { pubKey: currentPubKey } = useVegaWallet();
|
||||
const partyId = pubKey || currentPubKey;
|
||||
const { data, stopPolling } = useFundsAvailableQuery({
|
||||
variables: { partyId: partyId || '' },
|
||||
skip: !partyId,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
pollInterval: 5000,
|
||||
});
|
||||
|
||||
const fundsAvailable = data
|
||||
? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node))
|
||||
: undefined;
|
||||
const requiredFunds = data
|
||||
? BigNumber(data.networkParameter?.value || '0')
|
||||
: undefined;
|
||||
|
||||
const sumOfFunds =
|
||||
fundsAvailable
|
||||
?.filter((fa) => fa.balance)
|
||||
.reduce((sum, fa) => sum.plus(BigNumber(fa.balance)), BigNumber(0)) ||
|
||||
BigNumber(0);
|
||||
|
||||
if (requiredFunds && sumOfFunds.isGreaterThanOrEqualTo(requiredFunds)) {
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
return {
|
||||
fundsAvailable,
|
||||
requiredFunds,
|
||||
isEligible:
|
||||
fundsAvailable != null &&
|
||||
requiredFunds != null &&
|
||||
sumOfFunds.isGreaterThanOrEqualTo(requiredFunds),
|
||||
};
|
||||
};
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getNumberFormat } from '@vegaprotocol/utils';
|
||||
import { addDays } from 'date-fns';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import omit from 'lodash/omit';
|
||||
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
|
||||
|
||||
const STAKING_TIERS_MAPPING: Record<number, string> = {
|
||||
1: 'Tradestarter',
|
||||
2: 'Mid level degen',
|
||||
3: 'Reward hoarder',
|
||||
};
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const MOCK = {
|
||||
@@ -16,46 +10,76 @@ const MOCK = {
|
||||
currentReferralProgram: {
|
||||
id: 'abc',
|
||||
version: 1,
|
||||
endOfProgramTimestamp: addDays(new Date(), 10).toISOString(),
|
||||
windowLength: 10,
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumEpochs: 5,
|
||||
minimumRunningNotionalTakerVolume: '30000',
|
||||
referralDiscountFactor: '0.01',
|
||||
referralRewardFactor: '0.01',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 5,
|
||||
minimumRunningNotionalTakerVolume: '20000',
|
||||
referralDiscountFactor: '0.05',
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '100000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.05',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 5,
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
referralDiscountFactor: '0.001',
|
||||
referralRewardFactor: '0.001',
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.075',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '5000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.1',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '25000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.125',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '75000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.15',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '150000000',
|
||||
referralDiscountFactor: '0.07',
|
||||
referralRewardFactor: '0.175',
|
||||
},
|
||||
],
|
||||
stakingTiers: [
|
||||
{
|
||||
minimumStakedTokens: '10000',
|
||||
referralRewardMultiplier: '1',
|
||||
minimumStakedTokens: '100000000000000000000',
|
||||
referralRewardMultiplier: '1.025',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '20000',
|
||||
referralRewardMultiplier: '2',
|
||||
minimumStakedTokens: '1000000000000000000000',
|
||||
referralRewardMultiplier: '1.05',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '30000',
|
||||
referralRewardMultiplier: '3',
|
||||
minimumStakedTokens: '5000000000000000000000',
|
||||
referralRewardMultiplier: '1.1',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '50000000000000000000000',
|
||||
referralRewardMultiplier: '1.2',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '250000000000000000000000',
|
||||
referralRewardMultiplier: '1.25',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '500000000000000000000000',
|
||||
referralRewardMultiplier: '1.3',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '2024-12-31T01:00:00Z',
|
||||
windowLength: 30,
|
||||
},
|
||||
loading: false,
|
||||
error: undefined,
|
||||
},
|
||||
loading: false,
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
export const useReferralProgram = () => {
|
||||
@@ -79,9 +103,9 @@ export const useReferralProgram = () => {
|
||||
return {
|
||||
tier: i + 1, // sorted in asc order, hence first is the lowest tier
|
||||
rewardFactor: Number(t.referralRewardFactor),
|
||||
commission: Number(t.referralRewardFactor) * 100 + '%',
|
||||
commission: BigNumber(t.referralRewardFactor).times(100).toFixed(2) + '%',
|
||||
discountFactor: Number(t.referralDiscountFactor),
|
||||
discount: Number(t.referralDiscountFactor) * 100 + '%',
|
||||
discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%',
|
||||
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
|
||||
volume: getNumberFormat(0).format(
|
||||
Number(t.minimumRunningNotionalTakerVolume)
|
||||
@@ -90,13 +114,11 @@ export const useReferralProgram = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const stakingTiers = sortBy(
|
||||
data.currentReferralProgram.stakingTiers,
|
||||
(t) => t.referralRewardMultiplier
|
||||
const stakingTiers = sortBy(data.currentReferralProgram.stakingTiers, (t) =>
|
||||
parseFloat(t.referralRewardMultiplier)
|
||||
).map((t, i) => {
|
||||
return {
|
||||
tier: i + 1,
|
||||
label: STAKING_TIERS_MAPPING[i + 1],
|
||||
...t,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -75,10 +75,7 @@ export const useReferralToasts = () => {
|
||||
data-testid="toast-apply-code"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
const matched = matchPath(
|
||||
Routes.REFERRALS_APPLY_CODE,
|
||||
pathname
|
||||
);
|
||||
const matched = matchPath(Routes.REFERRALS, pathname);
|
||||
if (!matched) navigate(Routes.REFERRALS_APPLY_CODE);
|
||||
updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, {
|
||||
hidden: true,
|
||||
|
||||
@@ -2,7 +2,10 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { useRefereesQuery } from './__generated__/Referees';
|
||||
import compact from 'lodash/compact';
|
||||
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
|
||||
import type {
|
||||
ReferralSetsQuery,
|
||||
ReferralSetsQueryVariables,
|
||||
} from './__generated__/ReferralSets';
|
||||
import { useReferralSetsQuery } from './__generated__/ReferralSets';
|
||||
import { useStakeAvailable } from './use-stake-available';
|
||||
|
||||
@@ -118,3 +121,36 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
const retrieveReferralSetData = (data: ReferralSetsQuery | undefined) =>
|
||||
data?.referralSets.edges && data.referralSets.edges.length > 0
|
||||
? data.referralSets.edges[0]?.node
|
||||
: undefined;
|
||||
|
||||
export const useIsInReferralSet = (pubKey: string | null) => {
|
||||
const [asRefereeVariables, asRefereeSkip] = prepareVariables({
|
||||
pubKey,
|
||||
role: 'referee',
|
||||
});
|
||||
const [asReferrerVariables, asReferrerSkip] = prepareVariables({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
});
|
||||
|
||||
const { data: asRefereeData } = useReferralSetsQuery({
|
||||
variables: asRefereeVariables,
|
||||
skip: asRefereeSkip,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
const { data: asReferrerData } = useReferralSetsQuery({
|
||||
variables: asReferrerVariables,
|
||||
skip: asReferrerSkip,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
return Boolean(
|
||||
retrieveReferralSetData(asRefereeData) ||
|
||||
retrieveReferralSetData(asReferrerData)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ export const useStakeAvailable = (pubKey?: string) => {
|
||||
const { data } = useStakeAvailableQuery({
|
||||
variables: { partyId: partyId || '' },
|
||||
skip: !partyId,
|
||||
// TODO: remove when network params available
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
|
||||
@@ -15,11 +15,16 @@ export const LandingBanner = () => {
|
||||
</div>
|
||||
<div className="pt-20 sm:w-[50%]">
|
||||
<h1 className="text-6xl font-alpha calt mb-10">
|
||||
{t('Earn commission & stake rewards')}
|
||||
{t('Vega community referrals')}
|
||||
</h1>
|
||||
<p className="text-lg mb-1">
|
||||
{t(
|
||||
'Referral programs can be proposed and created via community governance.'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-lg mb-10">
|
||||
{t(
|
||||
'Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
|
||||
'Once live, users can generate referral codes to share with their friends and earn commission on their trades, while referred traders can access fee discounts based on the running volume of the group.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { ExternalLink, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../lib/use-t';
|
||||
|
||||
export const QUSDTooltip = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -275,30 +275,34 @@ jest.mock('@vegaprotocol/wallet', () => {
|
||||
});
|
||||
|
||||
describe('ReferralStatistics', () => {
|
||||
it('displays create code when no data has been found for given pubkey', () => {
|
||||
it('displays apply code when no data has been found for given pubkey', () => {
|
||||
const { queryByTestId } = render(
|
||||
<MockedProvider mocks={[]} showWarnings={false}>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={[]} showWarnings={false}>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(queryByTestId('referral-create-code-form')).toBeInTheDocument();
|
||||
expect(queryByTestId('referral-apply-code-form')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays referrer stats when given pubkey is a referrer', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
programMock,
|
||||
referralSetAsReferrerMock,
|
||||
noReferralSetAsRefereeMock,
|
||||
stakeAvailableMock,
|
||||
refereesMock,
|
||||
]}
|
||||
showWarnings={false}
|
||||
>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
programMock,
|
||||
referralSetAsReferrerMock,
|
||||
noReferralSetAsRefereeMock,
|
||||
stakeAvailableMock,
|
||||
refereesMock,
|
||||
]}
|
||||
showWarnings={false}
|
||||
>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -4,13 +4,10 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
ExternalLink,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { DEFAULT_AGGREGATION_DAYS, useReferral } from './hooks/use-referral';
|
||||
import { CreateCodeContainer } from './create-code-form';
|
||||
import classNames from 'classnames';
|
||||
import { Table } from './table';
|
||||
import {
|
||||
@@ -26,34 +23,39 @@ import compact from 'lodash/compact';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { useStakeAvailable } from './hooks/use-stake-available';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { useT, ns } from '../../lib/use-t';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { ApplyCodeForm } from './apply-code-form';
|
||||
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
|
||||
export const ReferralStatistics = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const program = useReferralProgram();
|
||||
|
||||
const { data: referee } = useReferral({
|
||||
const { data: referee, refetch: refereeRefetch } = useReferral({
|
||||
pubKey,
|
||||
role: 'referee',
|
||||
aggregationEpochs: program.details?.windowLength,
|
||||
});
|
||||
const { data: referrer } = useReferral({
|
||||
const { data: referrer, refetch: referrerRefetch } = useReferral({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
aggregationEpochs: program.details?.windowLength,
|
||||
});
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
refereeRefetch();
|
||||
referrerRefetch();
|
||||
}, [refereeRefetch, referrerRefetch]);
|
||||
|
||||
if (referee?.code) {
|
||||
return (
|
||||
<>
|
||||
<Statistics data={referee} program={program} as="referee" />;
|
||||
<Statistics data={referee} program={program} as="referee" />
|
||||
{!referee.isEligible && <ApplyCodeForm />}
|
||||
</>
|
||||
);
|
||||
@@ -62,13 +64,13 @@ export const ReferralStatistics = () => {
|
||||
if (referrer?.code) {
|
||||
return (
|
||||
<>
|
||||
<Statistics data={referrer} program={program} as="referrer" />;
|
||||
<Statistics data={referrer} program={program} as="referrer" />
|
||||
<RefereesTable data={referrer} program={program} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <CreateCodeContainer />;
|
||||
return <ApplyCodeFormContainer onSuccess={refetch} />;
|
||||
};
|
||||
|
||||
export const useStats = ({
|
||||
@@ -81,7 +83,9 @@ export const useStats = ({
|
||||
as?: 'referrer' | 'referee';
|
||||
}) => {
|
||||
const { benefitTiers } = program;
|
||||
const { data: epochData } = useCurrentEpochInfoQuery();
|
||||
const { data: epochData } = useCurrentEpochInfoQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const { data: statsData } = useReferralSetStatsQuery({
|
||||
variables: {
|
||||
code: data?.code || '',
|
||||
@@ -115,7 +119,7 @@ export const useStats = ({
|
||||
: 1;
|
||||
const finalCommissionValue = isNaN(multiplier)
|
||||
? baseCommissionValue
|
||||
: multiplier * baseCommissionValue;
|
||||
: new BigNumber(multiplier).times(baseCommissionValue).toNumber();
|
||||
|
||||
const discountFactorValue = refereeStats?.discountFactor
|
||||
? Number(refereeStats.discountFactor)
|
||||
@@ -174,6 +178,7 @@ export const Statistics = ({
|
||||
discountFactorValue,
|
||||
currentBenefitTierValue,
|
||||
epochsValue,
|
||||
nextBenefitTierValue,
|
||||
nextBenefitTierVolumeValue,
|
||||
nextBenefitTierEpochsValue,
|
||||
} = useStats({ data, program, as });
|
||||
@@ -207,6 +212,7 @@ export const Statistics = ({
|
||||
).toString(),
|
||||
}
|
||||
)}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{baseCommissionValue * 100}%
|
||||
</StatTile>
|
||||
@@ -229,22 +235,28 @@ export const Statistics = ({
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{multiplier || t('None')}
|
||||
</StatTile>
|
||||
);
|
||||
const baseCommissionFormatted = BigNumber(baseCommissionValue)
|
||||
.times(100)
|
||||
.toString();
|
||||
const finalCommissionFormatted = new BigNumber(finalCommissionValue)
|
||||
.times(100)
|
||||
.toString();
|
||||
const finalCommissionTile = (
|
||||
<StatTile
|
||||
title={t('Final commission rate')}
|
||||
description={
|
||||
!isNaN(multiplier)
|
||||
? `(${baseCommissionValue * 100}% ⨉ ${multiplier} = ${
|
||||
finalCommissionValue * 100
|
||||
}%)`
|
||||
? `(${baseCommissionFormatted}% ⨉ ${multiplier} = ${finalCommissionFormatted}%)`
|
||||
: undefined
|
||||
}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{finalCommissionValue * 100}%
|
||||
{finalCommissionFormatted}%
|
||||
</StatTile>
|
||||
);
|
||||
const numberOfTradersValue = data.referees.length;
|
||||
@@ -264,6 +276,7 @@ export const Statistics = ({
|
||||
title={t('myVolume', 'My volume (last {{count}} epochs)', {
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
})}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{compactNumFormat.format(referrerVolumeValue)}
|
||||
</StatTile>
|
||||
@@ -274,7 +287,7 @@ export const Statistics = ({
|
||||
.reduce((all, r) => all.plus(r), new BigNumber(0));
|
||||
const totalCommissionTile = (
|
||||
<StatTile
|
||||
title={t('totalCommission', 'Total commission (last {{count}}} epochs)', {
|
||||
title={t('totalCommission', 'Total commission (last {{count}} epochs)', {
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
})}
|
||||
description={<QUSDTooltip />}
|
||||
@@ -301,15 +314,25 @@ export const Statistics = ({
|
||||
);
|
||||
|
||||
const currentBenefitTierTile = (
|
||||
<StatTile title={t('Current tier')}>
|
||||
<StatTile
|
||||
title={t('Current tier')}
|
||||
description={
|
||||
nextBenefitTierValue?.tier
|
||||
? t('(Next tier: {{nextTier}})', {
|
||||
nextTier: nextBenefitTierValue?.tier,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{isApplyCodePreview
|
||||
? currentBenefitTierValue?.tier || benefitTiers[0]?.tier || 'None'
|
||||
: currentBenefitTierValue?.tier || 'None'}
|
||||
</StatTile>
|
||||
);
|
||||
const discountFactorTile = (
|
||||
<StatTile title={t('Discount')}>
|
||||
{isApplyCodePreview
|
||||
<StatTile title={t('Discount')} overrideWithNoProgram={!details}>
|
||||
{isApplyCodePreview && benefitTiers.length >= 1
|
||||
? benefitTiers[0].discountFactor * 100
|
||||
: discountFactorValue * 100}
|
||||
%
|
||||
@@ -324,6 +347,7 @@ export const Statistics = ({
|
||||
count: details?.windowLength,
|
||||
}
|
||||
)}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{compactNumFormat.format(runningVolumeValue)}
|
||||
</StatTile>
|
||||
@@ -332,14 +356,14 @@ export const Statistics = ({
|
||||
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
|
||||
);
|
||||
const nextTierVolumeTile = (
|
||||
<StatTile title={t('Volume to next tier')}>
|
||||
<StatTile title={t('Volume to next tier')} overrideWithNoProgram={!details}>
|
||||
{nextBenefitTierVolumeValue <= 0
|
||||
? '0'
|
||||
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const nextTierEpochsTile = (
|
||||
<StatTile title={t('Epochs to next tier')}>
|
||||
<StatTile title={t('Epochs to next tier')} overrideWithNoProgram={!details}>
|
||||
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -461,6 +485,7 @@ export const RefereesTable = ({
|
||||
count:
|
||||
details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
}}
|
||||
components={[<QUSDTooltip key="qusd" />]}
|
||||
ns={ns}
|
||||
/>
|
||||
),
|
||||
@@ -492,28 +517,3 @@ export const RefereesTable = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const QUSDTooltip = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,11 +4,10 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { HowItWorksTable } from './how-it-works-table';
|
||||
import { LandingBanner } from './landing-banner';
|
||||
import { TiersContainer } from './tiers';
|
||||
import { TabLink } from './buttons';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Outlet, useMatch } from 'react-router-dom';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useReferral } from './hooks/use-referral';
|
||||
@@ -22,12 +21,13 @@ import { ErrorBoundary } from '../../components/error-boundary';
|
||||
|
||||
const Nav = () => {
|
||||
const t = useT();
|
||||
const match = useMatch(Routes.REFERRALS_APPLY_CODE);
|
||||
return (
|
||||
<div className="flex justify-center border-b border-vega-cdark-500">
|
||||
<TabLink end to={Routes.REFERRALS}>
|
||||
{t('I want a code')}
|
||||
<TabLink end to={match ? Routes.REFERRALS_APPLY_CODE : Routes.REFERRALS}>
|
||||
{t('Apply code')}
|
||||
</TabLink>
|
||||
<TabLink to={Routes.REFERRALS_APPLY_CODE}>{t('I have a code')}</TabLink>
|
||||
<TabLink to={Routes.REFERRALS_CREATE_CODE}>{t('Create code')}</TabLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -96,15 +96,13 @@ export const Referrals = () => {
|
||||
<h2 className="text-2xl">{t('How it works')}</h2>
|
||||
</div>
|
||||
<div className="md:w-[60%] mx-auto">
|
||||
<HowItWorksTable />
|
||||
<div className="mt-5">
|
||||
<TradingAnchorButton
|
||||
className="mx-auto w-max"
|
||||
href={REFERRAL_DOCS_LINK}
|
||||
target="_blank"
|
||||
>
|
||||
{t('Read the terms')}{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
{t('Read the docs')} <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</TradingAnchorButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,10 @@ export const Tag = ({
|
||||
className={classNames(
|
||||
'w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
|
||||
{
|
||||
'border-vega-yellow-500 text-vega-yellow-500': color === 'yellow',
|
||||
'border-vega-green-500 text-vega-green-500': color === 'green',
|
||||
'border-vega-yellow-550 text-vega-yellow-550 dark:border-vega-yellow-500 dark:text-vega-yellow-500':
|
||||
color === 'yellow',
|
||||
'border-vega-green-550 text-vega-green-550 dark:border-vega-green-500 dark:text-vega-green-500':
|
||||
color === 'green',
|
||||
'border-vega-blue-500 text-vega-blue-500': color === 'blue',
|
||||
'border-vega-purple-500 text-vega-purple-500': color === 'purple',
|
||||
'border-vega-pink-500 text-vega-pink-500': color === 'pink',
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { Table } from './table';
|
||||
import classNames from 'classnames';
|
||||
import { BORDER_COLOR, GRADIENT } from './constants';
|
||||
import { Tag } from './tag';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ExternalLink, truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
DApp,
|
||||
DocsLinks,
|
||||
TOKEN_PROPOSAL,
|
||||
TOKEN_PROPOSALS,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { useT, ns } from '../../lib/use-t';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
// rainbow-ish order
|
||||
const TIER_COLORS: Array<ComponentProps<typeof Tag>['color']> = [
|
||||
'pink',
|
||||
'orange',
|
||||
'yellow',
|
||||
'green',
|
||||
'blue',
|
||||
'purple',
|
||||
];
|
||||
|
||||
const getTierColor = (tier: number) => {
|
||||
const tiers = Object.keys(TIER_COLORS).length;
|
||||
let index = Math.abs(tier - 1);
|
||||
if (tier >= tiers) {
|
||||
index = index % tiers;
|
||||
}
|
||||
return TIER_COLORS[index];
|
||||
};
|
||||
|
||||
const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -28,51 +51,63 @@ const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
|
||||
|
||||
const StakingTier = ({
|
||||
tier,
|
||||
label,
|
||||
referralRewardMultiplier,
|
||||
minimumStakedTokens,
|
||||
}: {
|
||||
tier: number;
|
||||
label: string;
|
||||
referralRewardMultiplier: string;
|
||||
minimumStakedTokens: string;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const color: Record<number, ComponentProps<typeof Tag>['color']> = {
|
||||
1: 'green',
|
||||
2: 'blue',
|
||||
3: 'pink',
|
||||
};
|
||||
const minimum = addDecimalsFormatNumber(minimumStakedTokens, 18);
|
||||
|
||||
// TODO: Decide what to do with the multiplier images
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const multiplierImage = (
|
||||
<div
|
||||
aria-hidden
|
||||
className={classNames(
|
||||
'w-full max-w-[80px] h-full min-h-[80px]',
|
||||
'bg-cover bg-right-bottom',
|
||||
{
|
||||
"bg-[url('/1x.png')]": tier === 1,
|
||||
"bg-[url('/2x.png')]": tier === 2,
|
||||
"bg-[url('/3x.png')]": tier === 3,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">{`${referralRewardMultiplier}x multiplier`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'overflow-hidden',
|
||||
'border rounded-md w-full',
|
||||
'flex flex-row',
|
||||
'bg-white dark:bg-vega-cdark-900',
|
||||
GRADIENT,
|
||||
BORDER_COLOR
|
||||
)}
|
||||
>
|
||||
<div aria-hidden className="max-w-[120px]">
|
||||
{tier < 4 && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={`/${tier}x.png`}
|
||||
alt={`${referralRewardMultiplier}x multiplier`}
|
||||
width={240}
|
||||
height={240}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'p-3 flex flex-row min-h-[80px] h-full items-center'
|
||||
)}
|
||||
</div>
|
||||
<div className={classNames('p-3')}>
|
||||
<Tag color={color[tier]}>Multiplier {referralRewardMultiplier}x</Tag>
|
||||
<h3 className="mt-1 mb-1 text-base">{label}</h3>
|
||||
<p className="text-sm text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{t('Stake a minimum of {{minimumStakedTokens}} $VEGA tokens', {
|
||||
minimumStakedTokens,
|
||||
})}
|
||||
</p>
|
||||
>
|
||||
<div>
|
||||
<Tag color={getTierColor(tier)}>
|
||||
{t('Multiplier')} {referralRewardMultiplier}x
|
||||
</Tag>
|
||||
<p className="mt-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
<Trans
|
||||
defaults="Stake a minimum of <0>{{minimum}}</0> $VEGA tokens"
|
||||
values={{ minimum }}
|
||||
components={[<b key={minimum}></b>]}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -91,21 +126,29 @@ export const TiersContainer = () => {
|
||||
|
||||
if ((!loading && !details) || error) {
|
||||
return (
|
||||
<div className="text-base px-5 py-10 text-center">
|
||||
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20 text-sm text-center">
|
||||
<Trans
|
||||
defaults="There are currently no active referral programs. Check the <0>Governance App</0> to see if there are any proposals in progress and vote."
|
||||
components={[
|
||||
<ExternalLink href={governanceLink(TOKEN_PROPOSALS)} key="link">
|
||||
<ExternalLink
|
||||
href={governanceLink(TOKEN_PROPOSALS)}
|
||||
key="link"
|
||||
className="underline"
|
||||
>
|
||||
{t('Governance App')}
|
||||
</ExternalLink>,
|
||||
]}
|
||||
ns={ns}
|
||||
/>
|
||||
/>{' '}
|
||||
<Trans
|
||||
defaults="You can propose a new program via the <0>Docs</0>."
|
||||
defaults="Use the <0>docs</0> tutorial to propose a new program."
|
||||
components={[
|
||||
<ExternalLink href={DocsLinks?.REFERRALS} key="link">
|
||||
{t('Docs')}
|
||||
<ExternalLink
|
||||
href={DocsLinks?.REFERRALS}
|
||||
key="link"
|
||||
className="underline"
|
||||
>
|
||||
{t('docs')}
|
||||
</ExternalLink>,
|
||||
]}
|
||||
ns={ns}
|
||||
@@ -116,47 +159,93 @@ export const TiersContainer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Benefit tiers */}
|
||||
<div className="flex flex-col items-baseline justify-between mt-10 mb-5">
|
||||
<h2 className="text-2xl">{t('Referral tiers')}</h2>
|
||||
<h2 className="text-3xl mt-10">{t('Current program details')}</h2>
|
||||
{details?.id && (
|
||||
<p>
|
||||
<Trans
|
||||
defaults="As a result of governance proposal <0>{{proposal}}</0> the program below is currently active on the Vega network."
|
||||
values={{ proposal: truncateMiddle(details.id) }}
|
||||
components={[
|
||||
<ExternalLink
|
||||
key="referral-program-proposal-link"
|
||||
href={governanceLink(TOKEN_PROPOSAL.replace(':id', details.id))}
|
||||
className="underline"
|
||||
>
|
||||
proposal
|
||||
</ExternalLink>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Meta */}
|
||||
<div className="mt-10 flex flex-row items-baseline justify-between text-xs text-vega-clight-100 dark:text-vega-cdark-100 font-alpha calt">
|
||||
{details?.id && (
|
||||
<span>
|
||||
{t('Proposal ID:')}{' '}
|
||||
<ExternalLink
|
||||
href={governanceLink(TOKEN_PROPOSAL.replace(':id', details.id))}
|
||||
>
|
||||
<span>{truncateMiddle(details.id)}</span>
|
||||
</ExternalLink>
|
||||
</span>
|
||||
)}
|
||||
{ends && (
|
||||
<span className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||
<span>
|
||||
{t('Program ends:')} {ends}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-20">
|
||||
{loading || !benefitTiers || benefitTiers.length === 0 ? (
|
||||
<Loading variant="large" />
|
||||
) : (
|
||||
<TiersTable
|
||||
windowLength={details?.windowLength}
|
||||
data={benefitTiers.map((bt) => ({
|
||||
...bt,
|
||||
tierElement: (
|
||||
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
|
||||
{bt.tier}
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Staking tiers */}
|
||||
<div className="flex flex-row items-baseline justify-between mb-5">
|
||||
<h2 className="text-2xl">{t('Staking multipliers')}</h2>
|
||||
</div>
|
||||
<div className="mb-20 flex flex-col justify-items-stretch lg:flex-row gap-5">
|
||||
{loading || !stakingTiers || stakingTiers.length === 0 ? (
|
||||
<>
|
||||
{/* Container */}
|
||||
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20">
|
||||
{/* Benefit tiers */}
|
||||
<div className="flex flex-col mb-5">
|
||||
<h3 className="text-2xl calt">{t('Benefit tiers')}</h3>
|
||||
<p className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||
{t(
|
||||
'Members of a referral group can access the increasing commission and discount benefits defined in the program based on their combined running volume.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
{loading || !benefitTiers || benefitTiers.length === 0 ? (
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
</>
|
||||
) : (
|
||||
<StakingTiers data={stakingTiers} />
|
||||
)}
|
||||
) : (
|
||||
<TiersTable
|
||||
windowLength={details?.windowLength}
|
||||
data={benefitTiers.map((bt) => ({
|
||||
...bt,
|
||||
tierElement: (
|
||||
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
|
||||
{bt.tier}
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Staking tiers */}
|
||||
<div className="flex flex-col mb-5">
|
||||
<h3 className="text-2xl calt">{t('Staking multipliers')}</h3>
|
||||
<p className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||
{t(
|
||||
'Referrers can access the commission multipliers defined in the program by staking VEGA tokens in the amounts shown.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="gap-5 grid lg:grid-cols-3">
|
||||
{loading || !stakingTiers || stakingTiers.length === 0 ? (
|
||||
<>
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
</>
|
||||
) : (
|
||||
<StakingTiers data={stakingTiers} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -168,17 +257,14 @@ const StakingTiers = ({
|
||||
data: ReturnType<typeof useReferralProgram>['stakingTiers'];
|
||||
}) => (
|
||||
<>
|
||||
{data.map(
|
||||
({ tier, label, referralRewardMultiplier, minimumStakedTokens }, i) => (
|
||||
<StakingTier
|
||||
key={i}
|
||||
tier={tier}
|
||||
label={label}
|
||||
referralRewardMultiplier={referralRewardMultiplier}
|
||||
minimumStakedTokens={minimumStakedTokens}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{data.map(({ tier, referralRewardMultiplier, minimumStakedTokens }, i) => (
|
||||
<StakingTier
|
||||
key={i}
|
||||
tier={tier}
|
||||
referralRewardMultiplier={referralRewardMultiplier}
|
||||
minimumStakedTokens={minimumStakedTokens}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -203,9 +289,17 @@ const TiersTable = ({
|
||||
{
|
||||
name: 'commission',
|
||||
displayName: t('Referrer commission'),
|
||||
tooltip: t('A percentage of commission earned by the referrer'),
|
||||
tooltip: t(
|
||||
"The proportion of the referee's taker fees to be rewarded to the referrer"
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'discount',
|
||||
displayName: t('Referee trading discount'),
|
||||
tooltip: t(
|
||||
"The proportion of the referee's taker fees to be discounted"
|
||||
),
|
||||
},
|
||||
{ name: 'discount', displayName: t('Referrer trading discount') },
|
||||
{
|
||||
name: 'volume',
|
||||
displayName: t(
|
||||
@@ -215,20 +309,34 @@ const TiersTable = ({
|
||||
count: windowLength,
|
||||
}
|
||||
),
|
||||
tooltip: t('The minimum running notional for the given benefit tier'),
|
||||
},
|
||||
{
|
||||
name: 'epochs',
|
||||
displayName: t('Min. epochs'),
|
||||
tooltip: t(
|
||||
'The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit'
|
||||
),
|
||||
},
|
||||
{ name: 'epochs', displayName: t('Min. epochs') },
|
||||
]}
|
||||
className="bg-white dark:bg-vega-cdark-900"
|
||||
data={data.map((d) => ({
|
||||
...d,
|
||||
className: classNames({
|
||||
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
|
||||
d.tier >= 3,
|
||||
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
|
||||
d.tier === 2,
|
||||
'from-vega-yellow-400 dark:from-vega-yellow-600 to-20% bg-highlight':
|
||||
'yellow' === getTierColor(d.tier),
|
||||
'from-vega-green-400 dark:from-vega-green-600 to-20% bg-highlight':
|
||||
'green' === getTierColor(d.tier),
|
||||
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
|
||||
d.tier === 1,
|
||||
'blue' === getTierColor(d.tier),
|
||||
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
|
||||
'purple' === getTierColor(d.tier),
|
||||
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
|
||||
'pink' === getTierColor(d.tier),
|
||||
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
|
||||
d.tier == 0,
|
||||
'orange' === getTierColor(d.tier),
|
||||
'from-vega-clight-200 dark:from-vega-cdark-200 to-20% bg-highlight':
|
||||
'none' === getTierColor(d.tier),
|
||||
}),
|
||||
}))}
|
||||
/>
|
||||
|
||||
@@ -34,8 +34,17 @@ type StatTileProps = {
|
||||
title: string;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
overrideWithNoProgram?: boolean;
|
||||
};
|
||||
export const StatTile = ({ title, description, children }: StatTileProps) => {
|
||||
export const StatTile = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
overrideWithNoProgram = false,
|
||||
}: StatTileProps) => {
|
||||
if (overrideWithNoProgram) {
|
||||
return <NoProgramTile title={title} />;
|
||||
}
|
||||
return (
|
||||
<Tile>
|
||||
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
|
||||
@@ -51,6 +60,20 @@ export const StatTile = ({ title, description, children }: StatTileProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const NoProgramTile = ({ title }: Pick<StatTileProps, 'title'>) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tile title={title}>
|
||||
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="text-xs text-vega-clight-300 dark:text-vega-cdark-300 leading-[3rem]">
|
||||
{t('No active program')}
|
||||
</div>
|
||||
</Tile>
|
||||
);
|
||||
};
|
||||
|
||||
const FADE_OUT_STYLE = classNames(
|
||||
'after:w-5 after:h-full after:absolute after:top-0 after:right-0',
|
||||
'after:bg-gradient-to-l after:from-vega-clight-800 after:dark:from-vega-cdark-800 after:to-transparent'
|
||||
|
||||
@@ -36,26 +36,6 @@ query RewardsPage($partyId: ID!) {
|
||||
}
|
||||
}
|
||||
|
||||
query ActivityStreak($partyId: ID!) {
|
||||
partiesConnection(id: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
activityStreak {
|
||||
activeFor
|
||||
isActive
|
||||
inactiveFor
|
||||
rewardDistributionMultiplier
|
||||
rewardVestingMultiplier
|
||||
epoch
|
||||
tradedVolume
|
||||
openVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query RewardsHistory(
|
||||
$partyId: ID!
|
||||
$epochRewardSummariesPagination: Pagination
|
||||
|
||||
@@ -10,13 +10,6 @@ export type RewardsPageQueryVariables = Types.Exact<{
|
||||
|
||||
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string } | null, activityStreak?: { __typename?: 'PartyActivityStreak', rewardVestingMultiplier: string, rewardDistributionMultiplier: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
|
||||
|
||||
export type ActivityStreakQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ActivityStreakQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, activityStreak?: { __typename?: 'PartyActivityStreak', activeFor: number, isActive: boolean, inactiveFor: number, rewardDistributionMultiplier: string, rewardVestingMultiplier: string, epoch: number, tradedVolume: string, openVolume: string } | null } }> } | null };
|
||||
|
||||
export type RewardsHistoryQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
epochRewardSummariesPagination?: Types.InputMaybe<Types.Pagination>;
|
||||
@@ -98,55 +91,6 @@ export function useRewardsPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
|
||||
export type RewardsPageQueryHookResult = ReturnType<typeof useRewardsPageQuery>;
|
||||
export type RewardsPageLazyQueryHookResult = ReturnType<typeof useRewardsPageLazyQuery>;
|
||||
export type RewardsPageQueryResult = Apollo.QueryResult<RewardsPageQuery, RewardsPageQueryVariables>;
|
||||
export const ActivityStreakDocument = gql`
|
||||
query ActivityStreak($partyId: ID!) {
|
||||
partiesConnection(id: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
activityStreak {
|
||||
activeFor
|
||||
isActive
|
||||
inactiveFor
|
||||
rewardDistributionMultiplier
|
||||
rewardVestingMultiplier
|
||||
epoch
|
||||
tradedVolume
|
||||
openVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useActivityStreakQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useActivityStreakQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useActivityStreakQuery` 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 } = useActivityStreakQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useActivityStreakQuery(baseOptions: Apollo.QueryHookOptions<ActivityStreakQuery, ActivityStreakQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ActivityStreakQuery, ActivityStreakQueryVariables>(ActivityStreakDocument, options);
|
||||
}
|
||||
export function useActivityStreakLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ActivityStreakQuery, ActivityStreakQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ActivityStreakQuery, ActivityStreakQueryVariables>(ActivityStreakDocument, options);
|
||||
}
|
||||
export type ActivityStreakQueryHookResult = ReturnType<typeof useActivityStreakQuery>;
|
||||
export type ActivityStreakLazyQueryHookResult = ReturnType<typeof useActivityStreakLazyQuery>;
|
||||
export type ActivityStreakQueryResult = Apollo.QueryResult<ActivityStreakQuery, ActivityStreakQueryVariables>;
|
||||
export const RewardsHistoryDocument = gql`
|
||||
query RewardsHistory($partyId: ID!, $epochRewardSummariesPagination: Pagination, $partyRewardsPagination: Pagination, $fromEpoch: Int, $toEpoch: Int) {
|
||||
epochRewardSummaries(
|
||||
|
||||
@@ -1,347 +0,0 @@
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { useRewardsHistoryQuery } from './__generated__/Rewards';
|
||||
import { useReferralProgram } from '../../client-pages/referrals/hooks/use-referral-program';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useRewardsRowData } from './use-reward-row-data';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Icon,
|
||||
type IconName,
|
||||
Intent,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
|
||||
export const ActiveRewards = ({
|
||||
epoch,
|
||||
pubKey,
|
||||
assets,
|
||||
}: {
|
||||
pubKey: string | null;
|
||||
epoch: number;
|
||||
assets: Record<string, AssetFieldsFragment>;
|
||||
}) => {
|
||||
const [epochVariables] = useState(() => ({
|
||||
from: epoch - 1,
|
||||
to: epoch,
|
||||
}));
|
||||
|
||||
// No need to specify the fromEpoch as it will by default give you the last
|
||||
const { refetch, data } = useRewardsHistoryQuery({
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
fromEpoch: epochVariables.from,
|
||||
toEpoch: epochVariables.to,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(refetch, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [refetch]);
|
||||
|
||||
const rowData = useRewardsRowData({
|
||||
epochRewardSummaries: data?.epochRewardSummaries,
|
||||
partyRewards: data?.party?.rewardsConnection,
|
||||
assets,
|
||||
partyId: pubKey,
|
||||
});
|
||||
|
||||
const t = useT();
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div className="pt-4">
|
||||
<p className="text-muted text-sm">{t('Not connected')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: extract card component - call it reward tiles
|
||||
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))]">
|
||||
{rowData.map((row, i) => {
|
||||
// TODO: filter out 0 values
|
||||
// const entries = Object.entries(row).filter(([key, value]) => {
|
||||
// value !== 0;
|
||||
// });
|
||||
return (
|
||||
<div key={i}>
|
||||
<div
|
||||
className={classNames(
|
||||
'bg-gradient-to-r col-span-full p-0.5 lg:col-auto h-full',
|
||||
'rounded-lg',
|
||||
'from-vega-blue-500 to-vega-green-400'
|
||||
)}
|
||||
>
|
||||
<div className="bg-gradient-to-b from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded p-4 flex flex-col gap-4">
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex flex-col gap-2 items-center text-center">
|
||||
<span className="flex items-center p-2 rounded-full border border-gray-600">
|
||||
<VegaIcon name={VegaIconNames.MAN} size={18} />
|
||||
</span>
|
||||
<span className="text-muted text-xs">
|
||||
{t('Individual')}
|
||||
</span>
|
||||
</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>
|
||||
{`${addDecimalsFormatNumber(
|
||||
row.total,
|
||||
row.asset?.decimals || 0
|
||||
)}`}
|
||||
</span>
|
||||
|
||||
<span>{`${row.asset?.symbol}`}</span>
|
||||
</span>
|
||||
|
||||
<Tooltip description={'pro rata'} underline={true}>
|
||||
<span className="text-xs">{t('Pro rata')}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 items-center text-center">
|
||||
<span className="flex items-center p-2 rounded-full border border-gray-600">
|
||||
<VegaIcon name={VegaIconNames.LOCK} size={18} />
|
||||
</span>
|
||||
<span className="text-muted text-xs whitespace-nowrap">
|
||||
{t('11 epochs')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="border-[0.5px] border-gray-700" />
|
||||
|
||||
<span>
|
||||
{t('Price taking')} • {row.asset?.symbol} • {row.asset?.name}
|
||||
</span>
|
||||
|
||||
<div className="flex items-center gap-8 flex-wrap">
|
||||
<span className="flex flex-col">
|
||||
<span className="text-muted text-xs">{t('Ends in')}</span>
|
||||
<span>{t('5 epochs')}</span>
|
||||
</span>
|
||||
|
||||
<span className="flex flex-col">
|
||||
<span className="text-muted text-xs">
|
||||
{t('Assessed over')}
|
||||
</span>
|
||||
<span>{t('5 epochs')}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span className="text-muted text-sm">
|
||||
{t(
|
||||
'Get rewards for taking prices on the order book and paying fees.'
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span className="border-[0.5px] border-gray-700" />
|
||||
|
||||
{/* <div className="grid grid-cols-3 items-center gap-3"> */}
|
||||
<div className="flex justify-between flex-wrap items-center gap-3">
|
||||
<span className="flex flex-col gap-1">
|
||||
<span className="flex items-center gap-1 text-muted text-xs">
|
||||
{t('Entity scope')}{' '}
|
||||
</span>
|
||||
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="flex items-center p-1 rounded-full border border-gray-600">
|
||||
<VegaIcon name={VegaIconNames.MAN} size={16} />
|
||||
</span>
|
||||
<StatusIndicator
|
||||
intent={Intent.Success}
|
||||
icon={IconNames.TICK_CIRCLE}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex flex-col gap-1">
|
||||
<span className="flex items-center gap-1 text-muted text-xs">
|
||||
{t('Amount staked')}{' '}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
{t('200 VEGA')}
|
||||
<StatusIndicator
|
||||
intent={Intent.Success}
|
||||
icon={IconNames.TICK_CIRCLE}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex flex-col gap-1">
|
||||
<span className="flex items-center gap-1 text-muted text-xs">
|
||||
{t('Average position')}{' '}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
{t('100 USDT')}
|
||||
<StatusIndicator
|
||||
intent={Intent.Success}
|
||||
icon={IconNames.TICK_CIRCLE}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// const getIconIntent = (status: string) => {
|
||||
// switch (status) {
|
||||
// case 'GOOD':
|
||||
// return { icon: IconNames.TICK_CIRCLE, intent: Intent.Success };
|
||||
// case 'RETIRED':
|
||||
// return { icon: IconNames.MOON, intent: Intent.None };
|
||||
// case 'UNKNOWN':
|
||||
// return { icon: IconNames.HELP, intent: Intent.Primary };
|
||||
// case 'MALICIOUS':
|
||||
// return { icon: IconNames.ERROR, intent: Intent.Danger };
|
||||
// case 'SUSPICIOUS':
|
||||
// return { icon: IconNames.ERROR, intent: Intent.Danger };
|
||||
// case 'COMPROMISED':
|
||||
// return { icon: IconNames.ERROR, intent: Intent.Danger };
|
||||
// default:
|
||||
// return { icon: IconNames.HELP, intent: Intent.Primary };
|
||||
// }
|
||||
// };
|
||||
|
||||
const StatusIndicator = ({
|
||||
intent,
|
||||
icon,
|
||||
}: {
|
||||
intent: Intent;
|
||||
icon: string;
|
||||
}) => {
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
{
|
||||
'text-gray-700 dark:text-gray-300': intent === Intent.None,
|
||||
'text-vega-blue': intent === Intent.Primary,
|
||||
'text-vega-green dark:text-vega-green': intent === Intent.Success,
|
||||
'dark:text-yellow text-yellow-600': intent === Intent.Warning,
|
||||
'text-vega-red': intent === Intent.Danger,
|
||||
},
|
||||
'flex items-start p-1 align-text-bottom'
|
||||
)}
|
||||
>
|
||||
<Icon size={3} name={icon as IconName} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const ActivityStreak = ({
|
||||
epoch,
|
||||
pubKey,
|
||||
assets,
|
||||
}: {
|
||||
pubKey: string | null;
|
||||
epoch: number;
|
||||
assets: Record<string, AssetFieldsFragment>;
|
||||
}) => {
|
||||
// const { data } = useActivityStreakQuery({
|
||||
// variables: {
|
||||
// partyId: pubKey || '',
|
||||
// },
|
||||
// });
|
||||
|
||||
const { benefitTiers } = useReferralProgram();
|
||||
|
||||
// const streaks = data?.partiesConnection?.edges?.map(
|
||||
// (edge) => edge?.node?.activityStreak
|
||||
// );
|
||||
|
||||
// @Input()
|
||||
const progress = 30;
|
||||
const total = 100;
|
||||
|
||||
const safeProgress = () => {
|
||||
return (progress / total) * 100;
|
||||
};
|
||||
|
||||
const progressBarHeight = 'h-6';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div
|
||||
className="grid"
|
||||
style={{
|
||||
gridTemplateColumns:
|
||||
'repeat(' + benefitTiers.length + ', minmax(0, 1fr))',
|
||||
}}
|
||||
>
|
||||
{benefitTiers.map((tier, index) => {
|
||||
return (
|
||||
<div key={index} className="flex justify-end -mr-10">
|
||||
<span className="flex flex-col items-center gap-1">
|
||||
<span className="flex flex-col items-center font-medium">
|
||||
<span className="text-sm">Tier {tier.tier}</span>
|
||||
<span className="text-muted text-xs">7 days</span>
|
||||
</span>
|
||||
|
||||
<span className="text-xs flex flex-col items-center justify-center px-2 py-1 rounded-lg text-white border border-pink-600 bg-pink-900">
|
||||
<span>Reward 1x</span>
|
||||
<span>Vesting 1.5x</span>
|
||||
</span>
|
||||
|
||||
<span className="text-pink-500 text-xl">•</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{benefitTiers.map((tier, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white dark:bg-gray-800 shadow-card rounded-[100px] grow"
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative w-full rounded-[100px] bg-gray-200 dark:bg-gray-800',
|
||||
progressBarHeight
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="absolute left-0 top-0 h-full rounded-[100px] bg-gradient-to-r from-vega-pink-600 to-vega-pink-500"
|
||||
style={{ width: safeProgress() + '%' }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<VegaIcon name={VegaIconNames.STREAK} />
|
||||
|
||||
<span className="flex flex-col text-xs">
|
||||
<span>4 days streak</span>
|
||||
<span>
|
||||
<span className="text-vega-pink-500">3 days</span> to Tier 1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div>{JSON.stringify(stakingTiers)}</div> */}
|
||||
{/* <div>{JSON.stringify(details)}</div> */}
|
||||
{/* <div>{JSON.stringify(benefitTiers)}</div> */}
|
||||
{/* <div>{JSON.stringify(streaks)}</div> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -33,7 +33,6 @@ import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { RewardsHistoryContainer } from './rewards-history';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
||||
import { ActiveRewards, ActivityStreak } from './activity-rewards';
|
||||
|
||||
const ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA = [
|
||||
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
|
||||
@@ -57,7 +56,6 @@ export const RewardsContainer = () => {
|
||||
|
||||
// No need to specify the fromEpoch as it will by default give you the last
|
||||
// Note activityStreak in query will fail
|
||||
|
||||
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
@@ -225,30 +223,6 @@ export const RewardsContainer = () => {
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
<Card
|
||||
title={t('Activity streak')}
|
||||
className="lg:col-span-full"
|
||||
loading={rewardsLoading}
|
||||
>
|
||||
<span className="flex flex-col mx-8">
|
||||
<ActivityStreak
|
||||
epoch={Number(epochData?.epoch.id)}
|
||||
pubKey={pubKey}
|
||||
assets={assetMap}
|
||||
/>
|
||||
</span>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('Active rewards')}
|
||||
className="lg:col-span-full"
|
||||
loading={rewardsLoading}
|
||||
>
|
||||
<ActiveRewards
|
||||
epoch={Number(epochData?.epoch.id)}
|
||||
pubKey={pubKey}
|
||||
assets={assetMap}
|
||||
/>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('Rewards history')}
|
||||
className="lg:col-span-full"
|
||||
|
||||
@@ -255,14 +255,6 @@ export const RewardHistoryTable = ({
|
||||
return colDefs;
|
||||
}, []);
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div className="pt-4">
|
||||
<p className="text-muted text-sm">{t('Not connected')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
@@ -350,7 +342,7 @@ export const RewardHistoryTable = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const EpochInput = ({
|
||||
const EpochInput = ({
|
||||
id,
|
||||
value,
|
||||
max,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
|
||||
|
||||
export const useOnboardingStore = create<{
|
||||
dialogOpen: boolean;
|
||||
walletDialogOpen: boolean;
|
||||
@@ -20,7 +21,7 @@ export const useOnboardingStore = create<{
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dialogOpen: true,
|
||||
dialogOpen: false,
|
||||
walletDialogOpen: false,
|
||||
dismissed: false,
|
||||
dismiss: () => set({ dismissed: true }),
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
import { useEffect } from 'react';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
||||
import { Connectors } from '../../lib/vega-connectors';
|
||||
import { RiskMessage } from './risk-message';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { RiskMessage } from './risk-message';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
import { ensureSuffix } from '@vegaprotocol/utils';
|
||||
|
||||
/**
|
||||
* A list of paths on which the welcome dialog should be omitted.
|
||||
*/
|
||||
const OMIT_ON_LIST = [ensureSuffix(Routes.REFERRALS, '/*')];
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { pathname } = useLocation();
|
||||
const t = useT();
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
|
||||
const walletDialogOpen = useOnboardingStore(
|
||||
(store) => store.walletDialogOpen
|
||||
);
|
||||
@@ -20,6 +31,16 @@ export const WelcomeDialog = () => {
|
||||
(store) => store.setWalletDialogOpen
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldOmit = OMIT_ON_LIST.map((path) =>
|
||||
matchPath(path, pathname)
|
||||
).some((m) => !!m);
|
||||
|
||||
if (dismissed || shouldOmit) return;
|
||||
|
||||
setDialogOpen(true);
|
||||
}, [dismissed, pathname, setDialogOpen]);
|
||||
|
||||
const content = walletDialogOpen ? (
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
@@ -31,7 +52,12 @@ export const WelcomeDialog = () => {
|
||||
<WelcomeDialogContent />
|
||||
);
|
||||
|
||||
const onClose = walletDialogOpen ? () => setWalletDialogOpen(false) : dismiss;
|
||||
const onClose = walletDialogOpen
|
||||
? () => setWalletDialogOpen(false)
|
||||
: () => {
|
||||
setDialogOpen(false);
|
||||
dismiss();
|
||||
};
|
||||
|
||||
const title = walletDialogOpen ? null : (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
||||
VEGA_VERSION=v0.73.6
|
||||
VEGA_VERSION=v0.73.8
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
|
||||
VEGA_VERSION=v0.73.6
|
||||
VEGA_VERSION=v0.73.8
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
|
||||
VEGA_VERSION=v0.73.6
|
||||
VEGA_VERSION=v0.73.8
|
||||
|
||||
Generated
+4
-4
@@ -1159,9 +1159,9 @@ profile = ["pytest-profiling", "snakeviz"]
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/vegaprotocol/vega-market-sim.git"
|
||||
reference = "HEAD"
|
||||
resolved_reference = "fbcb974b2055bbc80169cdfd69987f087f9969fb"
|
||||
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
|
||||
reference = "fix/genesis_panic"
|
||||
resolved_reference = "7ab04931924380db8000544b7f3d65fcb39b5467"
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
@@ -1342,4 +1342,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.11"
|
||||
content-hash = "d1231fe591b774e34b8f94a54cd02e4d7dae924c57785263841c3b0b0feed505"
|
||||
content-hash = "68ed0de55290a3b929d47eb7f7b031fb7e172261c7bbeb4f554b7c27a4462754"
|
||||
|
||||
@@ -31,7 +31,7 @@ initial_spread: float = 0.1
|
||||
market_name = "BTC:DAI_2023"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("vega", "page", "simple_market", "risk_accepted")
|
||||
@pytest.mark.usefixtures("vega", "page", "simple_market", "risk_accepted", "auth")
|
||||
def test_price_monitoring(simple_market, vega: VegaService, page: Page):
|
||||
page.goto(f"/#/markets/all")
|
||||
expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text(
|
||||
@@ -109,9 +109,8 @@ def test_price_monitoring(simple_market, vega: VegaService, page: Page):
|
||||
expect(
|
||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
||||
).to_have_text("100.00 (>100%)")
|
||||
|
||||
vega.forward("10s")
|
||||
vega.wait_fn(1)
|
||||
vega.wait_fn(10)
|
||||
vega.wait_for_total_catchup()
|
||||
expect(
|
||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
||||
|
||||
@@ -197,10 +197,10 @@ def test_market_info_risk_factors(page: Page):
|
||||
fields = [
|
||||
["Long", "0.05153"],
|
||||
["Short", "0.05422"],
|
||||
["Max Leverage Long", "19.036"],
|
||||
["Max Leverage Short", "18.111"],
|
||||
["Max Initial Leverage Long", "12.691"],
|
||||
["Max Initial Leverage Short", "12.074"],
|
||||
["Max Leverage Long", "19.406"],
|
||||
["Max Leverage Short", "18.445"],
|
||||
["Max Initial Leverage Long", "12.937"],
|
||||
["Max Initial Leverage Short", "12.297"],
|
||||
]
|
||||
validate_info_section(page, fields)
|
||||
|
||||
|
||||
@@ -137,6 +137,6 @@ def test_perps_market_terminated(page: Page, vega: VegaService):
|
||||
expect(page.get_by_test_id("market-trading-mode")).to_have_text("Trading modeNo trading")
|
||||
expect(page.get_by_test_id("market-state")).to_have_text("StatusClosed")
|
||||
expect(page.get_by_test_id("liquidity-supplied")).to_have_text("Liquidity supplied 0.00 (0.00%)")
|
||||
expect(page.get_by_test_id("market-funding")).to_have_text("Funding Rate / Countdown-Unknown")
|
||||
expect(page.get_by_test_id("index-price")).to_have_text("Index Price-")
|
||||
expect(page.get_by_test_id("market-funding")).to_contain_text("Funding Rate / Countdown")
|
||||
expect(page.get_by_test_id("index-price")).to_contain_text("Index Price")
|
||||
expect(page.get_by_test_id("deal-ticket-error-message-summary")).to_have_text("This market is closed and not accepting orders")
|
||||
@@ -279,6 +279,7 @@
|
||||
"This timestamp is user curated metadata and does not drive any on-chain functionality.": "This timestamp is user curated metadata and does not drive any on-chain functionality.",
|
||||
"Tier": "Tier",
|
||||
"to": "to",
|
||||
"To protect the network from spam, you must have at least {{requiredFunds}} qUSD of any asset on the network to proceed.": "To protect the network from spam, you must have at least {{requiredFunds}} qUSD of any asset on the network to proceed.",
|
||||
"Toast location": "Toast location",
|
||||
"Total discount": "Total discount",
|
||||
"Total distributed": "Total distributed",
|
||||
@@ -296,6 +297,9 @@
|
||||
"Trading on Market {{name}} may stop. There are open proposals to close this market": "Trading on Market {{name}} may stop. There are open proposals to close this market",
|
||||
"Trading on Market {{name}} will stop on {{date}}": "Trading on Market {{name}} will stop on {{date}}",
|
||||
"Transfer": "Transfer",
|
||||
"totalCommission": "Total commission (last {{count}} epochs)",
|
||||
"totalCommission_one": "Total commission (last {{count}} epoch)",
|
||||
"totalCommission_other": "Total commission (last {{count}} epochs)",
|
||||
"Unknown": "Unknown",
|
||||
"Unknown settlement date": "Unknown settlement date",
|
||||
"Vega Reward pot": "Vega Reward pot",
|
||||
|
||||
@@ -632,13 +632,9 @@ export const RiskFactorsInfoPanel = ({
|
||||
|
||||
const { short, long } = market.riskFactors;
|
||||
|
||||
const maxLeverageLong = new BigNumber(1).dividedBy(
|
||||
new BigNumber(market.linearSlippageFactor).plus(long)
|
||||
);
|
||||
const maxLeverageLong = new BigNumber(1).dividedBy(long);
|
||||
|
||||
const maxLeverageShort = new BigNumber(1).dividedBy(
|
||||
new BigNumber(market.linearSlippageFactor).plus(short)
|
||||
);
|
||||
const maxLeverageShort = new BigNumber(1).dividedBy(short);
|
||||
|
||||
const maxInitialLeverageLong = !market.tradableInstrument.marginCalculator
|
||||
? undefined
|
||||
|
||||
@@ -563,7 +563,7 @@ const WarningCell = ({
|
||||
<div className="flex items-center justify-end">
|
||||
{showIcon && (
|
||||
<span className="mr-2 text-black dark:text-white">
|
||||
<VegaIcon name={VegaIconNames.EXCLAMATION_MARK} size={12} />
|
||||
<VegaIcon name={VegaIconNames.EXCLAIMATION_MARK} size={12} />
|
||||
</span>
|
||||
)}
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
|
||||
@@ -94,7 +94,7 @@ export const ProtocolUpgradeCountdown = ({
|
||||
}
|
||||
)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EXCLAMATION_MARK} size={12} />{' '}
|
||||
<VegaIcon name={VegaIconNames.EXCLAIMATION_MARK} size={12} />{' '}
|
||||
<span className="flex flex-nowrap gap-1 whitespace-nowrap">
|
||||
<span>{t('Network upgrade in {{countdown}}', { countdown })} </span>
|
||||
</span>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const IconExclamationMark = ({ size = 16 }: { size: number }) => {
|
||||
export const IconExclaimationMark = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M8 0.879997L7.57 1.63L0.130005 14.5H15.87L8 0.879997ZM8.75 12H7.25V10.5H8.75V12ZM7.25 9.5V6H8.75V9.5H7.25Z" />
|
||||
@@ -1,28 +0,0 @@
|
||||
export const IconMan = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
className="stroke-current"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M8.00016 7.99996C9.47292 7.99996 10.6668 6.80605 10.6668 5.33329C10.6668 3.86053 9.47292 2.66663 8.00016 2.66663C6.5274 2.66663 5.3335 3.86053 5.3335 5.33329C5.3335 6.80605 6.5274 7.99996 8.00016 7.99996Z"
|
||||
stroke="#DCDEE3"
|
||||
stroke-width="1.33333"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
className="stroke-current"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.00033 9.33333C5.79119 9.33333 4.00033 11.1242 4.00033 13.3333V13.8667C4.00033 13.9403 3.94063 14 3.86699 14H2.80033C2.72669 14 2.66699 13.9403 2.66699 13.8667V13.3333C2.66699 10.3878 5.05481 8 8.00033 8C10.9458 8 13.3337 10.3878 13.3337 13.3333V13.8667C13.3337 13.9403 13.274 14 13.2003 14H12.1337C12.06 14 12.0003 13.9403 12.0003 13.8667V13.3333C12.0003 11.1242 10.2095 9.33333 8.00033 9.33333Z"
|
||||
fill="#DCDEE3"
|
||||
className="stroke-current"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
export const IconStreak = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="fillCurrent"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M5.999 4H3.99902V5.99997H5.999V4Z" fill="fillCurrent" />
|
||||
<path
|
||||
d="M7.999 2.0001V0.00012207H5.99902V2.0001V4.00007H7.999V2.0001Z"
|
||||
fill="fillCurrent"
|
||||
/>
|
||||
<path d="M3.99897 6H-0.000976562V7.99997H3.99897V6Z" fill="fillCurrent" />
|
||||
<path
|
||||
d="M5.999 7.99988H3.99902V9.99985H5.999V7.99988Z"
|
||||
fill="fillCurrent"
|
||||
/>
|
||||
<path
|
||||
d="M7.999 9.99994H5.99902V13.9999H7.999V9.99994Z"
|
||||
fill="fillCurrent"
|
||||
/>
|
||||
<path
|
||||
d="M9.999 7.99988H7.99902V9.99985H9.999V7.99988Z"
|
||||
fill="fillCurrent"
|
||||
/>
|
||||
<path d="M13.999 6H9.99902V7.99997H13.999V6Z" fill="fillCurrent" />
|
||||
<path d="M9.999 4H7.99902V5.99997H9.999V4Z" fill="fillCurrent" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
export const IconTeam = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon">
|
||||
<path
|
||||
id="Vector"
|
||||
d="M13.1282 11.1863C12.7371 10.7949 12.2801 10.4754 11.7782 10.2426C12.486 9.66912 12.9375 8.79412 12.9375 7.81287C12.9375 6.08162 11.4938 4.6613 9.76254 4.68787C8.05785 4.71443 6.68441 6.10349 6.68441 7.81287C6.68441 8.79412 7.13754 9.66912 7.84379 10.2426C7.34177 10.4753 6.88476 10.7947 6.49379 11.1863C5.64066 12.041 5.15629 13.1691 5.12504 14.3722C5.12462 14.3889 5.12755 14.4055 5.13364 14.421C5.13974 14.4366 5.14888 14.4507 5.16053 14.4627C5.17218 14.4746 5.1861 14.4841 5.20147 14.4906C5.21684 14.497 5.23336 14.5004 5.25004 14.5004H6.12504C6.19223 14.5004 6.24848 14.4472 6.25004 14.3801C6.27973 13.4738 6.64691 12.6254 7.29223 11.9816C7.62245 11.6496 8.01523 11.3865 8.44784 11.2073C8.88045 11.0281 9.3443 10.9366 9.81254 10.9379C10.7641 10.9379 11.6594 11.3082 12.3329 11.9816C12.9766 12.6254 13.3438 13.4738 13.375 14.3801C13.3766 14.4472 13.4329 14.5004 13.5 14.5004H14.375C14.3917 14.5004 14.4082 14.497 14.4236 14.4906C14.439 14.4841 14.4529 14.4746 14.4646 14.4627C14.4762 14.4507 14.4853 14.4366 14.4914 14.421C14.4975 14.4055 14.5005 14.3889 14.5 14.3722C14.4688 13.1691 13.9844 12.041 13.1282 11.1863ZM9.81254 9.81287C9.27816 9.81287 8.77504 9.60505 8.39848 9.22693C8.2095 9.03944 8.06022 8.8158 7.95955 8.56937C7.85888 8.32293 7.80888 8.05874 7.81254 7.79255C7.81723 7.28005 8.02191 6.78474 8.37973 6.41755C8.75473 6.03318 9.25629 5.81912 9.79223 5.81287C10.3219 5.80818 10.836 6.01443 11.2141 6.38474C11.6016 6.76443 11.8141 7.27224 11.8141 7.81287C11.8141 8.34724 11.6063 8.8488 11.2282 9.22693C11.0427 9.41333 10.822 9.56109 10.579 9.66167C10.336 9.76224 10.0755 9.81363 9.81254 9.81287ZM5.89848 8.22537C5.88441 8.08943 5.8766 7.95193 5.8766 7.81287C5.8766 7.56443 5.90004 7.32224 5.94379 7.0863C5.95473 7.03005 5.92504 6.97224 5.87348 6.9488C5.66098 6.85349 5.46566 6.72224 5.29691 6.55662C5.09807 6.36382 4.9416 6.13168 4.83748 5.87503C4.73337 5.61837 4.6839 5.34283 4.69223 5.06599C4.70629 4.56443 4.90785 4.08787 5.25941 3.72849C5.64535 3.33318 6.1641 3.11755 6.71566 3.1238C7.2141 3.12849 7.69535 3.32068 8.05941 3.6613C8.18285 3.77693 8.2891 3.90505 8.37817 4.04255C8.40942 4.09099 8.47035 4.1113 8.52348 4.09255C8.79848 3.99724 9.0891 3.93005 9.38754 3.8988C9.47504 3.88943 9.52504 3.79568 9.48598 3.71755C8.97816 2.71287 7.94066 2.01912 6.74066 2.00037C5.00785 1.9738 3.5641 3.39412 3.5641 5.1238C3.5641 6.10505 4.01566 6.98005 4.72348 7.55349C4.2266 7.78318 3.76879 8.10037 3.37191 8.49724C2.51566 9.35193 2.03129 10.4801 2.00004 11.6847C1.99962 11.7014 2.00255 11.718 2.00864 11.7335C2.01474 11.7491 2.02388 11.7632 2.03553 11.7752C2.04718 11.7871 2.0611 11.7966 2.07647 11.8031C2.09184 11.8095 2.10836 11.8129 2.12504 11.8129H3.0016C3.06879 11.8129 3.12504 11.7597 3.1266 11.6926C3.15629 10.7863 3.52348 9.93787 4.16879 9.29412C4.62816 8.83474 5.19066 8.51599 5.80473 8.3613C5.86566 8.34568 5.90629 8.28787 5.89848 8.22537Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -14,7 +14,7 @@ import { IconCopy } from './svg-icons/icon-copy';
|
||||
import { IconCross } from './svg-icons/icon-cross';
|
||||
import { IconDeposit } from './svg-icons/icon-deposit';
|
||||
import { IconEdit } from './svg-icons/icon-edit';
|
||||
import { IconExclamationMark } from './svg-icons/icon-exclamation-mark';
|
||||
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
|
||||
import { IconEye } from './svg-icons/icon-eye';
|
||||
import { IconEyeOff } from './svg-icons/icon-eye-off';
|
||||
import { IconForum } from './svg-icons/icon-forum';
|
||||
@@ -41,9 +41,6 @@ import { IconTwitter } from './svg-icons/icon-twitter';
|
||||
import { IconVote } from './svg-icons/icon-vote';
|
||||
import { IconWarning } from './svg-icons/icon-warning';
|
||||
import { IconWithdraw } from './svg-icons/icon-withdraw';
|
||||
import { IconMan } from './svg-icons/icon-man';
|
||||
import { IconTeam } from './svg-icons/icon-team';
|
||||
import { IconStreak } from './svg-icons/icon-streak';
|
||||
|
||||
export enum VegaIconNames {
|
||||
ARROW_DOWN = 'arrow-down',
|
||||
@@ -62,7 +59,7 @@ export enum VegaIconNames {
|
||||
CROSS = 'cross',
|
||||
DEPOSIT = 'deposit',
|
||||
EDIT = 'edit',
|
||||
EXCLAMATION_MARK = 'exclamation-mark',
|
||||
EXCLAIMATION_MARK = 'exclaimation-mark',
|
||||
EYE = 'eye',
|
||||
EYE_OFF = 'eye-off',
|
||||
FORUM = 'forum',
|
||||
@@ -79,7 +76,6 @@ export enum VegaIconNames {
|
||||
QUESTION_MARK = 'question-mark',
|
||||
SEARCH = 'search',
|
||||
STAR = 'star',
|
||||
STREAK = 'streak',
|
||||
SUN = 'sun',
|
||||
TICK = 'tick',
|
||||
TICKET = 'ticket',
|
||||
@@ -90,8 +86,6 @@ export enum VegaIconNames {
|
||||
VOTE = 'vote',
|
||||
WITHDRAW = 'withdraw',
|
||||
WARNING = 'warning',
|
||||
MAN = 'man',
|
||||
TEAM = 'team',
|
||||
}
|
||||
|
||||
export const VegaIconNameMap: Record<
|
||||
@@ -108,7 +102,7 @@ export const VegaIconNameMap: Record<
|
||||
'chevron-right': IconChevronRight,
|
||||
'chevron-up': IconChevronUp,
|
||||
'eye-off': IconEyeOff,
|
||||
'exclamation-mark': IconExclamationMark,
|
||||
'exclaimation-mark': IconExclaimationMark,
|
||||
'open-external': IconOpenExternal,
|
||||
'question-mark': IconQuestionMark,
|
||||
'trend-down': IconTrendDown,
|
||||
@@ -141,7 +135,4 @@ export const VegaIconNameMap: Record<
|
||||
vote: IconVote,
|
||||
withdraw: IconWithdraw,
|
||||
warning: IconWarning,
|
||||
man: IconMan,
|
||||
team: IconTeam,
|
||||
streak: IconStreak,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
shorten,
|
||||
titlefy,
|
||||
stripFullStops,
|
||||
ensureSuffix,
|
||||
} from './strings';
|
||||
|
||||
describe('truncateByChars', () => {
|
||||
@@ -88,3 +89,15 @@ describe('stripFullStops', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureSuffix', () => {
|
||||
it.each([
|
||||
['', 'abc', 'abc'],
|
||||
['abc', '', 'abc'],
|
||||
['def', 'abc', 'abcdef'],
|
||||
['ąę', 'ae', 'aeąę'],
|
||||
['🥪', '🍞+🔪=', '🍞+🔪=🥪'],
|
||||
])('ensures "%s" at the end of "%s": "%s"', (suffix, input, expected) => {
|
||||
expect(ensureSuffix(input, suffix)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,3 +33,9 @@ export function titlefy(words: (string | null | undefined)[]) {
|
||||
export function stripFullStops(input: string) {
|
||||
return input.replace(/\./g, '');
|
||||
}
|
||||
|
||||
export function ensureSuffix(input: string, suffix: string) {
|
||||
const maybeSuffix = input.substring(input.length - suffix.length);
|
||||
if (maybeSuffix === suffix) return input;
|
||||
return input + suffix;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user