fix(trading): rewordings in referrals, qusd tooltip, onboarding (#5477)
This commit is contained in:
parent
2221e9faca
commit
345be81142
@ -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 ? (
|
||||||
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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(
|
||||||
|
28
apps/trading/client-pages/referrals/qusd-tooltip.tsx
Normal file
28
apps/trading/client-pages/referrals/qusd-tooltip.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user