fix(trading): rewordings in referrals, qusd tooltip, onboarding (#5477)

This commit is contained in:
Art 2023-12-08 15:48:12 +01:00 committed by GitHub
parent 2221e9faca
commit 345be81142
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 72 deletions

View File

@ -18,13 +18,35 @@ import { Routes } from '../../lib/links';
import { useTransactionEventSubscription } from '@vegaprotocol/web3'; import { useTransactionEventSubscription } from '@vegaprotocol/web3';
import { Statistics, useStats } from './referral-statistics'; import { Statistics, useStats } from './referral-statistics';
import { useReferralProgram } from './hooks/use-referral-program'; 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 { useFundsAvailable } from './hooks/use-funds-available';
import { ViewType, useSidebar } from '../../components/sidebar'; import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; 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 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 validateCode = (value: string, t: ReturnType<typeof useT>) => {
const number = +`0x${value}`; const number = +`0x${value}`;
if (!value || value.length !== 64) { if (!value || value.length !== 64) {
@ -76,7 +98,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
setValue, setValue,
setError, setError,
watch, watch,
clearErrors,
} = useForm(); } = useForm();
const [params] = useSearchParams(); const [params] = useSearchParams();
@ -90,32 +111,11 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
*/ */
const validateFundsAvailable = useCallback(() => { const validateFundsAvailable = useCallback(() => {
if (requiredFunds && !isEligible) { if (requiredFunds && !isEligible) {
const err = t( const err = SPAM_PROTECTION_ERR;
'To protect the network from spam, you must have at least {{requiredFunds}} qUSD of any asset on the network to proceed.',
{
requiredFunds,
}
);
return err; return err;
} }
return true; return true;
}, [isEligible, requiredFunds, t]); }, [isEligible, requiredFunds]);
useEffect(() => {
if (codeField) {
const err = validateFundsAvailable();
if (err !== true) {
setStatus('no-funds');
setError('code', {
type: 'required',
message: err,
});
} else {
setStatus(null);
clearErrors('code');
}
}
}, [clearErrors, codeField, isEligible, setError, validateFundsAvailable]);
/** /**
* Validates the set a user tries to apply to. * Validates the set a user tries to apply to.
@ -140,6 +140,15 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
if (code) setValue('code', code); if (code) setValue('code', code);
}, [params, setValue]); }, [params, setValue]);
useEffect(() => {
const err = validateFundsAvailable();
if (err !== true) {
setStatus('no-funds');
} else {
setStatus(null);
}
}, [isEligible, validateFundsAvailable]);
const onSubmit = ({ code }: FieldValues) => { const onSubmit = ({ code }: FieldValues) => {
if (isReadOnly || !pubKey || !code || code.length === 0) { if (isReadOnly || !pubKey || !code || code.length === 0) {
return; return;
@ -323,10 +332,26 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
</label> </label>
<RainbowButton variant="border" {...getButtonProps()} /> <RainbowButton variant="border" {...getButtonProps()} />
</form> </form>
{errors.code && ( {status === 'no-funds' ? (
<InputError className="overflow-auto break-words"> <InputError intent="warning" className="overflow-auto break-words">
{errors.code.message?.toString()} <span>
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
</span>
</InputError> </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> </div>
{validateCode(codeField, t) === true && previewLoading && !previewData ? ( {validateCode(codeField, t) === true && previewLoading && !previewData ? (

View File

@ -1,7 +1,7 @@
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useFundsAvailableQuery } from './__generated__/FundsAvailable'; import { useFundsAvailableQuery } from './__generated__/FundsAvailable';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import sum from 'lodash/sum'; import BigNumber from 'bignumber.js';
/** /**
* Gets the funds for given public key and required min for * Gets the funds for given public key and required min for
@ -24,14 +24,16 @@ export const useFundsAvailable = (pubKey?: string) => {
? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node)) ? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node))
: undefined; : undefined;
const requiredFunds = data const requiredFunds = data
? BigInt(data.networkParameter?.value || '0') ? BigNumber(data.networkParameter?.value || '0')
: undefined; : undefined;
const sumOfFunds = sum( const sumOfFunds =
fundsAvailable?.filter((fa) => fa.balance).map((fa) => BigInt(fa.balance)) fundsAvailable
); ?.filter((fa) => fa.balance)
.reduce((sum, fa) => sum.plus(BigNumber(fa.balance)), BigNumber(0)) ||
BigNumber(0);
if (requiredFunds && sumOfFunds >= requiredFunds) { if (requiredFunds && sumOfFunds.isGreaterThanOrEqualTo(requiredFunds)) {
stopPolling(); stopPolling();
} }
@ -41,6 +43,6 @@ export const useFundsAvailable = (pubKey?: string) => {
isEligible: isEligible:
fundsAvailable != null && fundsAvailable != null &&
requiredFunds != null && requiredFunds != null &&
sumOfFunds >= requiredFunds, sumOfFunds.isGreaterThanOrEqualTo(requiredFunds),
}; };
}; };

View File

@ -15,7 +15,7 @@ export const LandingBanner = () => {
</div> </div>
<div className="pt-20 sm:w-[50%]"> <div className="pt-20 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10"> <h1 className="text-6xl font-alpha calt mb-10">
{t('Vega community referral program')} {t('Vega community referrals')}
</h1> </h1>
<p className="text-lg mb-1"> <p className="text-lg mb-1">
{t( {t(

View File

@ -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>
);
};

View File

@ -4,8 +4,6 @@ import {
VegaIcon, VegaIcon,
VegaIconNames, VegaIconNames,
truncateMiddle, truncateMiddle,
ExternalLink,
Tooltip,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
@ -28,10 +26,10 @@ import sortBy from 'lodash/sortBy';
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch'; import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { DocsLinks } from '@vegaprotocol/environment';
import { useT, ns } from '../../lib/use-t'; import { useT, ns } from '../../lib/use-t';
import { Trans } from 'react-i18next'; import { Trans } from 'react-i18next';
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form'; import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
import { QUSDTooltip } from './qusd-tooltip';
export const ReferralStatistics = () => { export const ReferralStatistics = () => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
@ -519,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>
);
};

View File

@ -159,11 +159,11 @@ export const TiersContainer = () => {
return ( return (
<> <>
<h2 className="text-3xl mt-10">{t('Current Program Details')}</h2> <h2 className="text-3xl mt-10">{t('Current program details')}</h2>
{details?.id && ( {details?.id && (
<p> <p>
<Trans <Trans
defaults="As a result of <0>{{proposal}}</0> the program below is currently active on the Vega network." 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) }} values={{ proposal: truncateMiddle(details.id) }}
components={[ components={[
<ExternalLink <ExternalLink

View File

@ -1,17 +1,23 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useMatch } from 'react-router-dom'; import { matchPath, useLocation } from 'react-router-dom';
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit'; import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { VegaConnectDialog } from '@vegaprotocol/wallet'; import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../../lib/vega-connectors'; import { Connectors } from '../../lib/vega-connectors';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { Links } from '../../lib/links'; import { Routes } from '../../lib/links';
import { RiskMessage } from './risk-message'; import { RiskMessage } from './risk-message';
import { WelcomeDialogContent } from './welcome-dialog-content'; import { WelcomeDialogContent } from './welcome-dialog-content';
import { useOnboardingStore } from './use-get-onboarding-step'; 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 = () => { export const WelcomeDialog = () => {
const isReferrals = useMatch(Links.REFERRALS()); const { pathname } = useLocation();
const t = useT(); const t = useT();
const { VEGA_ENV } = useEnvironment(); const { VEGA_ENV } = useEnvironment();
const dismissed = useOnboardingStore((store) => store.dismissed); const dismissed = useOnboardingStore((store) => store.dismissed);
@ -26,10 +32,14 @@ export const WelcomeDialog = () => {
); );
useEffect(() => { useEffect(() => {
if (dismissed) return; const shouldOmit = OMIT_ON_LIST.map((path) =>
if (isReferrals) return; matchPath(path, pathname)
).some((m) => !!m);
if (dismissed || shouldOmit) return;
setDialogOpen(true); setDialogOpen(true);
}, [dismissed, isReferrals, setDialogOpen]); }, [dismissed, pathname, setDialogOpen]);
const content = walletDialogOpen ? ( const content = walletDialogOpen ? (
<VegaConnectDialog <VegaConnectDialog

View File

@ -4,6 +4,7 @@ import {
shorten, shorten,
titlefy, titlefy,
stripFullStops, stripFullStops,
ensureSuffix,
} from './strings'; } from './strings';
describe('truncateByChars', () => { 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);
});
});

View File

@ -33,3 +33,9 @@ export function titlefy(words: (string | null | undefined)[]) {
export function stripFullStops(input: string) { export function stripFullStops(input: string) {
return input.replace(/\./g, ''); 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;
}