fix(#315): misc styling and ui fixes for the token app

* chore: add callout loaders and input lozenges

* fix: text colors and nav heading

* fix: text color for home links

* chore: fix spacing of wallets

* chore: fix missing translation keys

* chore: add loader to pending associatino tx callout, fix spacing of text within callout

* chore: make sure etherscan links open in a new tab

* fix: redemption page

* fix: spacing of rewards tables list

* fix: link styles on withdraw page

* fix: styles for withdrawal table

* fix: footer links

* fix: staking page links and spacing

* fix: translations

* fix: spacing of callout title, spacing of staking connect step

* fix: vesting page title

* fix: proposals list spacing

* fix: proposal page and vote details

* chore: update translation of metamask wallet connection button

* chore: delete unused files

* chore: dont nest buttons inside  links

* chore: lint

* fix: title test after text change
This commit is contained in:
Matthew Russell 2022-06-07 11:24:43 -07:00 committed by GitHub
parent 458e9cafc3
commit a66be425be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 422 additions and 724 deletions

View File

@ -6,7 +6,7 @@ describe('token', () => {
it('should always have a header title based on environment', () => {
cy.get('[data-testid="header-title"]', { timeout: 8000 }).should(
'have.text',
`${fairgroundSet ? 'Fairground token' : '$VEGA TOKEN'}`
`${fairgroundSet ? 'Fairground token' : 'VEGA TOKEN'}`
);
});
});

View File

@ -1,14 +1,12 @@
import './i18n';
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { BrowserRouter as Router } from 'react-router-dom';
import { AppLoader } from './app-loader';
import { AppBanner } from './components/app-banner';
import { AppFooter } from './components/app-footer';
import { BalanceManager } from './components/balance-manager';
import { EthWallet } from './components/eth-wallet';
import { GraphQlProvider } from './components/graphql-provider';
import { TemplateSidebar } from './components/page-templates/template-sidebar';
import { TransactionModal } from './components/transactions-modal';
import { VegaWallet } from './components/vega-wallet';
@ -21,11 +19,12 @@ import { Connectors } from './lib/web3-connectors';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
import { client } from './lib/apollo-client';
function App() {
const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []);
return (
<GraphQlProvider>
<ApolloProvider client={client}>
<Router>
<EnvironmentProvider>
<AppStateProvider>
@ -55,7 +54,7 @@ function App() {
</AppStateProvider>
</EnvironmentProvider>
</Router>
</GraphQlProvider>
</ApolloProvider>
);
}

View File

@ -1,3 +1,4 @@
import { Link } from '@vegaprotocol/ui-toolkit';
import { Trans } from 'react-i18next';
import { Links } from '../../config';
@ -13,8 +14,12 @@ export const AppFooter = () => {
i18nKey="footerLinksText"
components={{
/* eslint-disable */
feedbackLink: <a href={Links.FEEDBACK} />,
githubLink: <a href={Links.GITHUB} />,
feedbackLink: (
<Link className="text-white underline" href={Links.FEEDBACK} />
),
githubLink: (
<Link className="text-white underline" href={Links.GITHUB} />
),
/* eslint-enable */
}}
/>

View File

@ -9,7 +9,7 @@ interface BulletHeaderProps {
export const BulletHeader = ({ tag, children, style }: BulletHeaderProps) => {
return React.createElement(
tag,
{ className: 'mt-24 pt-8 pb-20 uppercase', style },
{ className: 'mt-24 pt-8 pb-20 uppercase text-white', style },
<>
<span className="inline-block w-[12px] h-[12px] mr-12 bg-white" />
{children}

View File

@ -1,39 +0,0 @@
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { Ethereum } from '../icons';
import { Button } from '@vegaprotocol/ui-toolkit';
interface EthWalletContainerProps {
children: (address: string) => React.ReactElement;
}
export const EthWalletContainer = ({ children }: EthWalletContainerProps) => {
const { t } = useTranslation();
const { appDispatch } = useAppState();
const { account } = useWeb3React();
if (!account) {
return (
<Button
data-testid="connect-to-eth-btn"
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
>
<div>{t('connectEthWallet')}</div>
<Ethereum />
</Button>
);
}
return children(account);
};

View File

@ -1 +0,0 @@
export * from './eth-wallet-container';

View File

@ -163,15 +163,15 @@ const ConnectedKey = () => {
/>
)}
<WalletCardActions>
<Link style={{ flex: 1 }} to={`${Routes.STAKING}/associate`}>
<Button variant="primary" className="w-full">
<Link className="flex-1" to={`${Routes.STAKING}/associate`}>
<span className="flex items-center justify-center w-full text-center px-28 border h-28">
{t('associate')}
</Button>
</span>
</Link>
<Link style={{ flex: 1 }} to={`${Routes.STAKING}/disassociate`}>
<Button variant="primary" className="w-full">
<Link className="flex-1" to={`${Routes.STAKING}/disassociate`}>
<span className="flex items-center justify-center w-full px-28 border h-28">
{t('disassociate')}
</Button>
</span>
</Link>
</WalletCardActions>
</>
@ -187,7 +187,7 @@ export const EthWallet = () => {
return (
<WalletCard>
<WalletCardHeader>
<h1 className="text-h3 px-8 uppercase">{t('ethereumKey')}</h1>
<h1 className="text-h3 uppercase">{t('ethereumKey')}</h1>
{account && (
<div className="font-mono px-4 text-right">
<div>{truncateMiddle(account)}</div>
@ -215,8 +215,8 @@ export const EthWallet = () => {
{account ? (
<ConnectedKey />
) : (
<Button
className="fill button-secondary--inverted"
<button
className="w-full px-28 border h-28"
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
@ -226,7 +226,7 @@ export const EthWallet = () => {
data-test-id="connect-to-eth-wallet-button"
>
{t('connectEthWalletToAssociate')}
</Button>
</button>
)}
{account && (
<WalletCardActions>

View File

@ -1,12 +0,0 @@
import { ApolloProvider } from '@apollo/client';
import React from 'react';
import { client } from '../../lib/apollo-client';
export const GraphQlProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

View File

@ -1 +0,0 @@
export * from './graphql-provider';

View File

@ -7,7 +7,7 @@ export const Heading = ({ title }: HeadingProps) => {
return (
<header className="my-0 mx-auto">
<h1 className="font-alpha font-normal text-h3 uppercase m-0 mb-4">
<h1 className="font-alpha calt text-h3 text-white uppercase mb-4">
{title}
</h1>
</header>

View File

@ -1 +0,0 @@
export * from './loader';

View File

@ -1,35 +0,0 @@
import React from 'react';
interface LoaderProps {
invert?: boolean;
}
export const Loader = ({ invert = false }: LoaderProps) => {
const [, forceRender] = React.useState(false);
React.useEffect(() => {
const interval = setInterval(() => {
forceRender((x) => !x);
}, 100);
return () => clearInterval(interval);
}, []);
return (
<span className="flex flex-row flex-wrap w-[15px] h-[15px]">
{new Array(9).fill(null).map((_, i) => {
return (
<span
key={i}
style={{
opacity: Math.random() > 0.5 ? 1 : 0,
}}
className={`block w-5 h-5 opacity-0 ${
invert ? 'bg-black' : 'bg-white'
}`}
/>
);
})}
</span>
);
};

View File

@ -15,7 +15,7 @@ const ProgressContents = ({
}) => (
<div
className={`flex justify-between py-2 font-mono ${
light ? 'gap-0 px-0 text-black' : 'gap-y-0 gap-x-4 px-4 text-black-60'
light ? 'gap-0 px-0 text-black' : 'gap-y-0 gap-x-4 px-4'
}`}
>
{children}

View File

@ -5,14 +5,13 @@ import debounce from 'lodash/debounce';
import React from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { useTranslation } from 'react-i18next';
import { Link, NavLink } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { Flags } from '../../config';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import vegaWhite from '../../images/vega_white.png';
import { Routes } from '../../routes/router-config';
import { EthWallet } from '../eth-wallet';
import { VegaWallet } from '../vega-wallet';
@ -36,14 +35,14 @@ export const Nav = () => {
return (
<div
className={`p-8 ${
className={`p-16 ${
inverted
? 'bg-clouds bg-no-repeat bg-cover bg-vega-yellow'
: 'border-b-white border-b-1'
}`}
>
{isDesktop && <NavHeader fairground={inverted} />}
<div className="flex justify-between items-center mx-auto gap-12 lg:justify-start lg:ml-8">
<div className="flex justify-between items-center mx-auto gap-12 lg:justify-start">
{!isDesktop && <NavHeader fairground={inverted} />}
<div className="flex gap-12 lg:flex-auto">
{isDesktop ? (
@ -61,76 +60,10 @@ const NavHeader = ({ fairground }: { fairground: boolean }) => {
const { t } = useTranslation();
return (
<div className="h-[30px] inline-flex items-center ml-8 lg:h-40 uppercase">
<Link to="/">
{fairground ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="33"
height="20"
viewBox="0 0 200 116"
fill="none"
data-testid="fairground-icon"
>
<g clip-path="url(#clip0)">
<path
d="M70.5918 32.8569L70.5918 22.7932L60.5254 22.7932L60.5254 32.8569L70.5918 32.8569Z"
fill="black"
></path>
<path
d="M80.6641 83.2006L80.6641 73.1377L70.5977 73.1377L70.5977 83.2006L80.6641 83.2006Z"
fill="black"
></path>
<path
d="M70.5918 93.2409L70.5918 83.1772L60.5254 83.1772L60.5254 93.2409L70.5918 93.2409Z"
fill="black"
></path>
<path
d="M100.797 93.2636L100.797 73.1377L90.7305 73.1377L90.7305 93.2636L100.797 93.2636Z"
fill="black"
></path>
<path
d="M90.7285 103.33L90.7285 93.2671L80.662 93.2671L80.662 103.33L90.7285 103.33Z"
fill="black"
></path>
<path
d="M90.7285 22.8026L90.7285 12.74L80.662 12.74L80.662 22.8026L90.7285 22.8026Z"
fill="black"
></path>
<path
d="M110.869 12.6108L110.869 2.54785L100.803 2.54785L100.803 12.6108L110.869 12.6108Z"
fill="black"
></path>
<path
d="M120.934 103.326L120.934 73.1377L110.867 73.1377L110.867 103.326L120.934 103.326Z"
fill="black"
></path>
<path
d="M110.869 113.384L110.869 103.321L100.803 103.321L100.803 113.384L110.869 113.384Z"
fill="black"
></path>
<path
d="M161.328 52.9855L161.328 42.9226L151.262 42.9226L151.262 52.9855L161.328 52.9855Z"
fill="black"
></path>
<path
d="M20.133 83.187L30.3354 83.187L30.3354 73.124L40.4017 73.124L40.4017 63.0613L50.4681 63.0613L50.4681 73.124L60.5345 73.124L60.5345 63.0613L70.6008 63.0613L80.6672 63.0613L131.135 63.0613L131.135 113.376L161.334 113.376L161.334 103.313L171.4 103.313L171.4 93.25L181.467 93.25L181.467 83.187L191.533 83.187L191.533 63.0613L181.467 63.0613L181.467 73.1241L171.4 73.1241L171.4 83.187L161.334 83.187L161.334 73.1241L171.4 73.1241L171.4 63.0613L161.334 63.0613L151.268 63.0613L141.201 63.0613L141.201 52.9983L141.201 32.8726L161.334 32.8726L171.4 32.8726L171.4 63.0613L181.467 63.0613L181.467 52.9983L191.533 52.9983L191.533 32.8726L181.467 32.8726L181.467 22.8096L171.4 22.8096L171.4 12.7467L161.334 12.7467L161.334 2.54785L141.201 2.54785L131.135 2.54785L131.135 52.9983L120.933 52.9983L120.933 12.7467L110.866 12.7467L110.866 52.9983L100.8 52.9983L100.8 22.8096L90.7336 22.8096L90.7336 52.9983L80.6672 52.9983L80.6672 32.8726L70.6008 32.8726L70.6008 52.9983L60.5345 52.9983L60.5345 42.9354L50.4681 42.9354L50.4681 52.9983L40.4017 52.9983L40.4017 42.9354L30.3354 42.9354L30.3354 32.8726L20.133 32.8726L20.133 22.8096L0.000308081 22.8096L0.000307201 42.9354L10.0666 42.9354L10.0666 52.9983L20.133 52.9983L20.133 63.0613L10.0666 63.0613L10.0666 73.124L0.000305881 73.124L0.000305002 93.25L20.133 93.25L20.133 83.187Z"
fill="black"
></path>
</g>
<defs>
<clipPath id="clip0">
<rect width="200" height="116" fill="none"></rect>
</clipPath>
</defs>
</svg>
) : (
<img alt="Vega" src={vegaWhite} className="w-[30px] lg:w-40" />
)}
</Link>
<div className="h-[30px] inline-flex items-center lg:h-40 uppercase">
<h1
data-testid="header-title"
className={`text-[28px] lg:text-h3 pl-8 ${
className={`text-h3 font-alpha uppercase calt mb-2 ${
fairground ? 'text-black' : 'text-white'
}`}
>

View File

@ -12,7 +12,7 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
<div className="border-b border-white lg:grid lg:grid-rows-[auto_minmax(600px,_1fr)] lg:grid-cols-[1fr_450px]">
<Nav />
<main className="p-20">{children}</main>
<aside className="hidden lg:block lg:col-start-2 lg:col-end-3 lg:row-start-1 lg: row-end-3 p-20 bg-banner bg-cover border-l border-white">
<aside className="hidden lg:block lg:col-start-2 lg:col-end-3 lg:row-start-1 lg: row-end-3 p-20 bg-banner bg-contain border-l border-white">
{sidebar.map((Component, i) => (
<section className="mb-20 last:mb-0" key={i}>
{Component}

View File

@ -1 +0,0 @@
export * from './stateful-button';

View File

@ -1,14 +0,0 @@
import type { ButtonHTMLAttributes } from 'react';
import { Button } from '@vegaprotocol/ui-toolkit';
export const StatefulButton = (
props: ButtonHTMLAttributes<HTMLButtonElement>
) => {
const classProp = props.className || '';
return (
<Button
{...props}
className={`flex justify-center items-center gap-12 disabled:cursor-default ${classProp}`}
/>
);
};

View File

@ -4,6 +4,7 @@ import {
Input,
Intent,
FormGroup,
Lozenge,
} from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -25,7 +26,6 @@ export const AmountInput = ({
amount,
setAmount,
maximum,
// TODO: render currency in input when https://github.com/vegaprotocol/frontend-monorepo/issues/273
currency,
}: {
amount: string;
@ -36,25 +36,31 @@ export const AmountInput = ({
const { t } = useTranslation();
return (
<div className="flex">
<Input
data-testid="token-amount-input"
className="flex-1"
name={inputName}
id={inputName}
onChange={(e) => setAmount(e.target.value)}
value={amount}
autoComplete="off"
type="number"
max={maximum.toString()}
min={0}
step="any"
/>
<div className="flex-1">
<Input
data-testid="token-amount-input"
name={inputName}
id={inputName}
onChange={(e) => setAmount(e.target.value)}
value={amount}
autoComplete="off"
type="number"
max={maximum.toString()}
min={0}
step="any"
appendElement={
<Lozenge className="text-[10px] relative top-[-2px]">
{currency}
</Lozenge>
}
/>
</div>
{maximum && (
<Button
variant="inline-link"
onClick={() => setAmount(maximum.toString())}
data-testid="token-amount-use-maximum"
className="flex flex-col justify-center text-ui p-8 h-28 my-0 mx-8"
className="flex flex-col justify-center p-8 h-28 my-0 mx-8"
>
{t('Use maximum')}
</Button>
@ -69,7 +75,6 @@ export const TokenInput = ({
perform,
submitText,
currency,
approveText,
allowance,
approve,
@ -84,7 +89,6 @@ export const TokenInput = ({
perform: () => void;
submitText: string;
currency: string;
requireApproval?: boolean;
maximum?: BigNumber;
minimum?: BigNumber;
@ -128,6 +132,7 @@ export const TokenInput = ({
new BigNumber(amount).isLessThan(minimum)
);
}, [amount, isApproved, maximum, requireApproval, minimum]);
let approveContent = null;
if (showApproveButton) {
@ -158,11 +163,9 @@ export const TokenInput = ({
}
} else if (requireApproval) {
approveContent = (
<Callout
iconName="tick"
intent={Intent.Success}
title={`${currency} are approved for staking`}
/>
<Callout iconName="tick" intent={Intent.Success}>
<p>{`${currency} are approved for staking`}</p>
</Callout>
);
}

View File

@ -1 +0,0 @@
export * from './transaction-button';

View File

@ -1,159 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import type { TransactionState } from '../../hooks/transaction-reducer';
import { TxState } from '../../hooks/transaction-reducer';
import { truncateMiddle } from '../../lib/truncate-middle';
import { Button, Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { Error, HandUp, Tick } from '../icons';
import { Loader } from '../loader';
import { StatefulButton } from '../stateful-button';
interface TransactionButtonProps {
text: string;
transactionState: TransactionState;
/** txHash of the transaction if already complete */
forceTxHash: string | null;
forceTxState?: TxState;
disabled?: boolean;
start: () => void;
reset: () => void;
}
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<div className="mb-20">{children}</div>
);
const Text = ({ children }: { children: React.ReactNode }) => (
<p className="flex justify-center items-center gap-10 m-0 py-12 px-32 leading-[1.15]">
{children}
</p>
);
export const TransactionButton = ({
text,
transactionState,
forceTxHash,
forceTxState,
disabled = false,
start,
reset,
}: TransactionButtonProps) => {
const { t } = useTranslation();
const { txState, txData } = transactionState;
const txHash = forceTxHash || txData.hash;
const state = forceTxState || txState;
if (state === TxState.Complete) {
return (
<Wrapper>
<Text>
<span className="text-vega-green">
<Tick />
</span>
<span>{t('txButtonComplete')}</span>
</Text>
<TransactionButtonFooter txHash={txHash} />
</Wrapper>
);
}
// User as started transaction and we are awaiting confirmation from the users wallet
if (state === TxState.Requested) {
return (
<Wrapper>
<StatefulButton disabled={true}>
<HandUp />
<span>{t('txButtonActionRequired')}</span>
</StatefulButton>
<TransactionButtonFooter
message={t('transactionHashPrompt')}
txHash={txHash}
/>
</Wrapper>
);
}
if (state === TxState.Pending) {
return (
<Wrapper>
<StatefulButton disabled={true}>
<Loader />
<span>{t('txButtonAwaiting')}</span>
</StatefulButton>
<TransactionButtonFooter txHash={txHash} />
</Wrapper>
);
}
if (state === TxState.Error) {
return (
<Wrapper>
<Text>
<span className="text-intent-danger">
<Error />
</span>
<span>{t('txButtonFailure')}</span>
<Button onClick={reset} variant="inline-link">
{t('Try again')}
</Button>
</Text>
<TransactionButtonFooter txHash={txHash} />
</Wrapper>
);
}
// Idle
return (
<Wrapper>
<StatefulButton onClick={start} disabled={disabled}>
{text}
</StatefulButton>
<TransactionButtonFooter txHash={txHash} />
</Wrapper>
);
};
interface TransactionButtonFooterProps {
txHash: string | null;
message?: string;
}
export const TransactionButtonFooter = ({
txHash,
message,
}: TransactionButtonFooterProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
if (message) {
return (
<div className="mt-4 mb-0 mx-0">
<p className="m-0 py-4 pl-8 border-l border-[3px] border-intent-warning text-ui">
<span className="relative top-2 mr-4 text-intent-warning">
<Error />
</span>
{message}
</p>
</div>
);
}
if (txHash) {
return (
<div className="transaction-button__footer">
<p className="flex justify-between items-start m-0 text-ui">
<span>{t('transaction')}</span>
<Link
href={`${ETHERSCAN_URL}/tx/${txHash}`}
title={t('View on Etherscan')}
>
{truncateMiddle(txHash)}
</Link>
</p>
</div>
);
}
return null;
};

View File

@ -23,13 +23,18 @@ export const TransactionComplete = ({
intent={Intent.Success}
title={heading || t('Complete')}
>
{body && <p data-testid="transaction-complete-body">{body}</p>}
<p>
{body && (
<p className="mb-8" data-testid="transaction-complete-body">
{body}
</p>
)}
<p className="mb-8">
<Link
title={t('View transaction on Etherscan')}
target="_blank"
href={`${ETHERSCAN_URL}/tx/${hash}`}
>
{hash}
{t('View transaction on Etherscan')}
</Link>
</p>
{footer && <p data-testid="transaction-complete-footer">{footer}</p>}

View File

@ -20,12 +20,15 @@ export const TransactionError = ({
return (
<Callout iconName="error" intent={Intent.Danger}>
<p>{error ? error.message : t('Something went wrong')}</p>
<p className="mb-8">
{error ? error.message : t('Something went wrong')}
</p>
{hash ? (
<p>
<p className="mb-8">
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${hash}`}
target="_blank"
>
{hash}
</Link>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Callout } from '@vegaprotocol/ui-toolkit';
import { Callout, Loader } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
@ -37,11 +37,16 @@ export const TransactionPending = ({
return defaultTitle;
}, [heading, remainingConfirmations, t]);
return (
<Callout iconName="refresh" title={title}>
{body && <p data-testid="transaction-pending-body">{body}</p>}
<p>
<Callout icon={<Loader size="small" />} title={title}>
{body && (
<p className="mb-8" data-testid="transaction-pending-body">
{body}
</p>
)}
<p className="mb-8">
<Link
title={t('View transaction on Etherscan')}
target="_blank"
href={`${ETHERSCAN_URL}/tx/${hash}`}
>
{hash}

View File

@ -7,7 +7,7 @@ export const TransactionRequested = () => {
<Callout
iconName="hand-up"
intent={Intent.Prompt}
title={t('Awaiting action in Ethereum wallet (e.g. metamask)')}
title={t('Awaiting action in Ethereum wallet (e.g. MetaMask)')}
/>
);
};

View File

@ -85,6 +85,7 @@ export const TransactionModal = () => {
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`}
target="_blank"
>
{truncateMiddle(transaction.tx.hash)}
</Link>

View File

@ -1,31 +1,22 @@
import { Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Links } from '../../config';
export const DownloadWalletPrompt = () => {
const { t } = useTranslation();
return (
<>
<div className="mt-8">
<h3>{t('getWallet')}</h3>
<p style={{ margin: 0 }}>
<a
className={'text-deemphasise'}
href={Links.WALLET_GUIDE}
target="_blank"
rel="noreferrer"
>
<p>
<Link className="text-deemphasise" href={Links.WALLET_GUIDE}>
{t('readGuide')}
</a>
</Link>
</p>
<p style={{ margin: 0 }}>
<a
className={'text-deemphasise'}
href={Links.WALLET_RELEASES}
target="_blank"
rel="noreferrer"
>
<p>
<Link className="text-deemphasise" href={Links.WALLET_RELEASES}>
{t('downloadWallet')}
</a>
</Link>
</p>
</>
</div>
);
};

View File

@ -41,10 +41,8 @@ export const VegaWallet = () => {
<WalletCard dark={true}>
<WalletCardHeader dark={true}>
<div>
<h1 className="text-h3 px-8 uppercase">{t('vegaWallet')}</h1>
<span className="mx-8 text-h6">
{keypair && `(${keypair.name})`}
</span>
<h1 className="text-h3 uppercase">{t('vegaWallet')}</h1>
<span className="text-h6">{keypair && `(${keypair.name})`}</span>
</div>
{keypair && (
<span className="font-mono px-8">
@ -72,6 +70,7 @@ const VegaWalletNotConnected = () => {
})
}
variant="secondary"
className="w-full"
data-testid="connect-vega"
>
{t('connectVegaWalletToUseAssociated')}
@ -182,10 +181,14 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
))}
<WalletCardActions>
<Link style={{ flex: 1 }} to={Routes.GOVERNANCE}>
<Button className="w-full">{t('governance')}</Button>
<span className="flex items-center justify-center w-full px-28 border h-28 bg-white text-black">
{t('governance')}
</span>
</Link>
<Link style={{ flex: 1 }} to={Routes.STAKING}>
<Button className="w-full">{t('staking')}</Button>
<span className="flex items-center justify-center w-full px-28 border h-28 bg-white text-black">
{t('staking')}
</span>
</Link>
</WalletCardActions>
<VegaWalletAssetList accounts={accounts} />

View File

@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react';
import { Link } from 'react-router-dom';
@ -27,15 +28,13 @@ interface WalletCardProps {
dark?: boolean;
}
export const WalletCard = ({ dark, children }: WalletCardProps) => (
<div
className={`text-ui border border-white ${
dark ? 'bg-black text-white' : 'bg-white text-black'
}`}
>
{children}
</div>
);
export const WalletCard = ({ dark, children }: WalletCardProps) => {
const className = classNames('text-ui border border-white', 'p-8', {
'bg-black text-white': dark,
'bg-white text-black': !dark,
});
return <div className={className}>{children}</div>;
};
interface WalletCardHeaderProps {
children: React.ReactNode;
@ -44,7 +43,7 @@ interface WalletCardHeaderProps {
export const WalletCardHeader = ({ children }: WalletCardHeaderProps) => {
return (
<div className="flex justify-between items-center py-8">{children}</div>
<div className="flex gap-4 justify-between items-center">{children}</div>
);
};
@ -53,7 +52,7 @@ interface WalletCardContentProps {
}
export const WalletCardContent = ({ children }: WalletCardContentProps) => {
return <div className="my-4 mx-8">{children}</div>;
return <div className="mt-8">{children}</div>;
};
export const WalletCardRow = ({

View File

@ -1,38 +0,0 @@
import * as React from 'react';
export function useCopyToClipboard() {
const [copied, setCopied] = React.useState(false);
// Once copied flip a boolean so we can display
// a message to the user such as 'Copied!' which will
// revert after 800 milliseconds
React.useEffect(() => {
let timeout: ReturnType<typeof setTimeout>;
if (copied) {
timeout = setTimeout(() => {
setCopied(false);
}, 800);
}
return () => clearTimeout(timeout);
}, [copied]);
// Create an input we can copy text from and render it
// off screen
function handler(text: string) {
const input = document.createElement('input');
input.style.position = 'fixed';
input.style.left = '100vw';
input.style.opacity = '0';
input.value = text;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
setCopied(true);
}
return {
copy: handler,
copied,
};
}

View File

@ -1,9 +1,9 @@
{
"Home": "Home",
"fairgroundTitle": "Fairground token",
"pageTitleHome": "$VEGA Tokens",
"pageTitleHome": "The $VEGA token",
"pageTitleClaim": "Claim $VEGA tokens",
"pageTitleAssociate": "Associate $VEGA tokens with $VEGA Key",
"pageTitleAssociate": "Associate $VEGA tokens with Vega Key",
"pageTitleRedemption": "Vesting",
"pageTitleLiquidity": "Incentivised Liquidity Programme",
"pageTitleRedemptionTranche": "Redeem from Tranche",
@ -79,6 +79,7 @@
"Incomplete": "Incomplete",
"Complete": "Complete",
"View on Etherscan (opens in a new tab)": "View on Etherscan (opens in a new tab)",
"View transaction on Etherscan": "View transaction on Etherscan",
"Transaction in progress": "Transaction in progress",
"Unknown error": "Unknown error",
"Awaiting action in Ethereum wallet (e.g. MetaMask)": "Awaiting action in Ethereum wallet (e.g. MetaMask)",
@ -126,7 +127,6 @@
"noVestingTokens": "You do not have any vesting $VEGA tokens. Switch to another Ethereum address to check what can be redeemed, or view <tranchesLink>all tranches</tranchesLink>",
"ethereumKey": "Ethereum key",
"checkingForProvider": "Checking for provider",
"Awaiting action in wallet...": "Awaiting action in Ethereum wallet (e.g. MetaMask)",
"In wallet": "In wallet",
"Not staked": "Not staked",
"viewKeys": "View keys",
@ -149,7 +149,6 @@
"Stake VEGA tokens": "Stake $VEGA tokens",
"Tranche breakdown": "Tranche breakdown",
"Across all tranches": "Across all tranches",
"theVegaToken": "The {{symbol}} token",
"Token Vesting": "Vesting",
"Rewards": "Rewards",
"Governance": "Governance",
@ -238,7 +237,7 @@
"VEGA Tokens": "$VEGA Tokens",
"SLP Tokens": "SLP Tokens",
"Connected Vega key": "Connected Vega key",
"What Vega wallet/key is going to control your stake?": "What Vega Wallet/key is going to control your stake?",
"What Vega key is going to control your stake?": "What Vega key is going to control your stake?",
"Where would you like to stake from?": "Where would you like to associate from?",
"associateNoVega": "Your connected Ethereum address does not have any $VEGA",
"associateInfo1": "To participate in governance or to nominate a Validator you'll need to associate $VEGA tokens with a Vega wallet/key.",
@ -322,7 +321,7 @@
"Associate VEGA tokens": "Associate $VEGA tokens",
"VEGA token holders can vote on proposed changes to the network and create proposals.": "$VEGA token holders can vote on proposed changes to the network and create proposals.",
"VEGA token holders can nominate a validator node and receive staking rewards.": "$VEGA token holders can nominate a validator node and receive staking rewards.",
"USE YOUR VEGA TOKENS": "USE YOUR $VEGA TOKENS",
"Use your Vega tokens": "Use your Vega tokens",
"Check your vesting VEGA tokens": "Check your vesting $VEGA tokens",
"Tokens from this Tranche have been redeemed": "Tokens from this Tranche have been redeemed",
"You have redeemed {{redeemedAmount}} VEGA tokens from this tranche. They are now free to transfer from your Ethereum wallet.": "You have redeemed {{redeemedAmount}} $VEGA tokens from this tranche. They are now free to transfer from your Ethereum wallet.",
@ -450,12 +449,12 @@
"transaction": "Transaction",
"associated": "Associated",
"notAssociated": "Not Associated",
"title": "$VEGA TOKEN",
"title": "VEGA TOKEN",
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas.": "Connect to the Ethereum wallet that holds your $VEGA tokens to see what can be redeemed from vesting tranches. To redeem tokens you will need some ETH to pay gas fees.\n",
"Staked on Vega validator": "Staked $VEGA",
"You can associate tokens while they are held in the vesting contract, when they unlock you will need to disassociate them before they can be redeemed.": "You can associate tokens while they are held in the vesting contract, when they unlock you will need to disassociate them before they can be redeemed.",
"Nominate Stake to Validator Node": "Select a validator to nominate",
"Vega key {{vegaKey}} can now participate in governance and Nominate a validator with it's stake.": "Vega key {{vegaKey}} can now participate in governance and nominate a validator with your associated $VEGA.",
"successfullAssociationMessage": "Vega key {{vegaKey}} can now participate in governance and nominate a validator with your associated $VEGA.",
"stakingStep2Text": "Your tokens need to be associated with a Vega Wallet so that you can control your stake",
"stakingStep3": "Step 3. Select the validator you'd like to nominate",
"stakeAddSuccessMessage": "You can cancel your nomination at any time",

View File

@ -38,6 +38,7 @@ export const Complete = ({
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${commitTxHash}`}
target="_blank"
>
{commitTxHash}
</Link>
@ -49,6 +50,7 @@ export const Complete = ({
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${claimTxHash}`}
target="_blank"
>
{claimTxHash}
</Link>

View File

@ -14,8 +14,9 @@ const Contracts = () => {
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>{key}:</div>
<Link
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${value}`}
target="_blank"
>
{value}
</Link>

View File

@ -10,7 +10,7 @@ export const ProposalTermsJson = ({
const { t } = useTranslation();
return (
<section>
<h2>{t('proposalTerms')}</h2>
<h2 className="text-h4 text-white mb-8">{t('proposalTerms')}</h2>
<SyntaxHighlighter data={terms} />
</section>
);

View File

@ -37,6 +37,7 @@ export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => {
data-testid="proposal-votes-table"
muted={true}
numerical={true}
headingLevel={4}
>
<KeyValueTableRow>
{t('willPass')}

View File

@ -20,58 +20,67 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
return <p>{t('noProposals')}</p>;
}
const renderRow = (proposal: Proposals_proposals) => {
if (!proposal || !proposal.id) return null;
return (
<li className="last:mb-0 mb-24" key={proposal.id}>
<Link to={proposal.id} className="underline">
<header>{getProposalName(proposal)}</header>
</Link>
<KeyValueTable muted={true}>
<KeyValueTableRow>
{t('state')}
<span data-testid="governance-proposal-state">
<CurrentProposalState proposal={proposal} />
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{isFuture(new Date(proposal.terms.closingDatetime))
? t('closesOn')
: t('closedOn')}
<span data-testid="governance-proposal-closingDate">
{format(
new Date(proposal.terms.closingDatetime),
DATE_FORMAT_DETAILED
)}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{isFuture(new Date(proposal.terms.enactmentDatetime))
? t('proposedEnactment')
: t('enactedOn')}
<span data-testid="governance-proposal-enactmentDate">
{format(
new Date(proposal.terms.enactmentDatetime),
DATE_FORMAT_DETAILED
)}
</span>
</KeyValueTableRow>
</KeyValueTable>
</li>
);
};
return (
<>
<Heading title={t('pageTitleGovernance')} />
<p>{t('proposedChangesToVegaNetwork')}</p>
<p>{t('vegaTokenHoldersCanVote')}</p>
<p>{t('requiredMajorityDescription')}</p>
<h2>{t('proposals')}</h2>
<ul>{proposals.map((row) => renderRow(row))}</ul>
<p className="mb-8">{t('proposedChangesToVegaNetwork')}</p>
<p className="mb-8">{t('vegaTokenHoldersCanVote')}</p>
<p className="mb-8">{t('requiredMajorityDescription')}</p>
<h2 className="text-h4 text-white">{t('proposals')}</h2>
<ul>
{proposals.map((proposal) => (
<ProposalListItem proposal={proposal} />
))}
</ul>
</>
);
};
interface ProposalListItemProps {
proposal: Proposals_proposals;
}
const ProposalListItem = ({ proposal }: ProposalListItemProps) => {
const { t } = useTranslation();
if (!proposal || !proposal.id) return null;
return (
<li className="last:mb-0 mb-24" key={proposal.id}>
<Link to={proposal.id} className="underline text-white">
<header>{getProposalName(proposal)}</header>
</Link>
<KeyValueTable muted={true}>
<KeyValueTableRow>
{t('state')}
<span data-testid="governance-proposal-state">
<CurrentProposalState proposal={proposal} />
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{isFuture(new Date(proposal.terms.closingDatetime))
? t('closesOn')
: t('closedOn')}
<span data-testid="governance-proposal-closingDate">
{format(
new Date(proposal.terms.closingDatetime),
DATE_FORMAT_DETAILED
)}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{isFuture(new Date(proposal.terms.enactmentDatetime))
? t('proposedEnactment')
: t('enactedOn')}
<span data-testid="governance-proposal-enactmentDate">
{format(
new Date(proposal.terms.enactmentDatetime),
DATE_FORMAT_DETAILED
)}
</span>
</KeyValueTableRow>
</KeyValueTable>
</li>
);
};

View File

@ -3,10 +3,8 @@ import { format } from 'date-fns';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
ProposalState,
VoteValue,
} from '../../../../__generated__/globalTypes';
import { ProposalState } from '../../../../__generated__/globalTypes';
import { VoteValue } from '../../../../__generated__/globalTypes';
import {
AppStateActionType,
useAppState,
@ -141,9 +139,8 @@ export const VoteButtons = ({
: 'text-intent-danger';
return (
<p>
<span>{t('youVoted')}</span>{' '}
<span className={className}>{t(`voteState_${voteState}`)}</span>
{'. '}
<span>{t('youVoted')}:</span>{' '}
<span className={className}>{t(`voteState_${voteState}`)}</span>{' '}
{voteDatetime ? (
<span>{format(voteDatetime, DATE_FORMAT_LONG)}. </span>
) : null}
@ -167,17 +164,11 @@ export const VoteButtons = ({
}
return (
<div className="flex">
<Button
onClick={() => submitVote(VoteValue.Yes)}
className="w-[calc(50%_-_7px)] mt-4 mr-12"
>
<div className="flex gap-4">
<Button onClick={() => submitVote(VoteValue.Yes)} className="flex-1">
{t('voteFor')}
</Button>
<Button
onClick={() => submitVote(VoteValue.No)}
className="w-[calc(50%_-_7px)] mt-4"
>
<Button onClick={() => submitVote(VoteValue.No)} className="flex-1">
{t('voteAgainst')}
</Button>
</div>

View File

@ -44,15 +44,15 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
return (
<section>
<h3>{t('votes')}</h3>
<p className="mb-0">
<h3 className="text-h4 text-white mb-8">{t('votes')}</h3>
<p className="mb-8">
<span>
<CurrentProposalStatus proposal={proposal} />
</span>
.&nbsp;
{proposal.state === ProposalState.Open ? daysLeft : null}
</p>
<table className="w-full font-normal">
<table className="w-full font-normal mb-12">
<thead>
<tr>
<th className="text-vega-green w-[18%] text-left">{t('for')}</th>
@ -86,13 +86,13 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
{formatNumber(yesTokens, defaultDecimals)}
</td>
<td></td>
<td className="text-white-60">
<td className="text-white-60 text-right">
{formatNumber(noTokens, defaultDecimals)}
</td>
</tr>
</tbody>
</table>
<p>
<p className="mb-8">
{t('participation')}
{': '}
{participationMet ? (
@ -109,7 +109,7 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
</p>
{keypair ? (
<>
<h3>{t('yourVote')}</h3>
<h3 className="text-h4 text-white mb-8">{t('yourVote')}</h3>
<VoteButtonsContainer
voteState={voteState}
castVote={castVote}

View File

@ -8,21 +8,21 @@ export const VoteProgress = ({
progress: BigNumber;
}) => {
return (
<>
<div className="w-full h-4 relative">
<div
data-testid="vote-progress-indicator"
className="relative top-[10px] w-[1px] h-16 bg-white z-[1]"
className="absolute top-[-5px] w-[1px] h-16 bg-white z-[1]"
style={{ left: `${threshold}%` }}
/>
<div className="bp3-progress-bar bp3-no-stripes bg-intent-danger rounded-none h-5">
<div className="w-full h-4">
<div
className="bp3-progress-meter bg-vega-green rounded-none"
className="absolute left-0 bg-vega-green h-4"
data-testid="vote-progress-bar"
style={{
width: `${progress}%`,
}}
/>
</div>
</>
</div>
);
};

View File

@ -34,7 +34,7 @@ const Home = ({ name }: RouteChildProps) => {
return (
<>
<Heading title={t('theVegaToken', { symbol: '$VEGA' })} />
<Heading title={t('pageTitleHome')} />
<HomeSection>
<TokenDetails
totalSupply={appState.totalSupply}
@ -42,7 +42,7 @@ const Home = ({ name }: RouteChildProps) => {
/>
</HomeSection>
<HomeSection>
<h2 className="text-h4">{t('Token Vesting')}</h2>
<h2 className="text-h4 text-white">{t('Token Vesting')}</h2>
<p className="mb-8">
{t(
'The vesting contract holds VEGA tokens until they have become unlocked.'
@ -68,7 +68,7 @@ const Home = ({ name }: RouteChildProps) => {
</Link>
</HomeSection>
<HomeSection>
<h2 className="text-h4">{t('USE YOUR VEGA TOKENS')}</h2>
<h2 className="text-h4 text-white">{t('Use your Vega tokens')}</h2>
<p className="mb-8">
{t(
'To use your tokens on the Vega network they need to be associated with a Vega wallet/key.'
@ -83,6 +83,7 @@ const Home = ({ name }: RouteChildProps) => {
<a
data-test-id="get-vega-wallet-link"
href={Links.WALLET_GUIDE}
className="underline text-white"
target="_blank"
rel="nofollow noreferrer"
>
@ -90,15 +91,18 @@ const Home = ({ name }: RouteChildProps) => {
</a>
</p>
<p data-test-id="associate-vega-tokens-link-on-homepage">
<Link to={`${Routes.STAKING}/associate`}>
<Link
to={`${Routes.STAKING}/associate`}
className="underline text-white"
>
{t('Associate VEGA tokens')}
</Link>
</p>
</HomeSection>
<div style={{ display: 'flex', gap: 36 }}>
<div style={{ flex: 1 }}>
<div className="flex gap-40">
<div className="flex-1">
<HomeSection>
<h2 className="text-h4">{t('Staking')}</h2>
<h2 className="text-h4 text-white">{t('Staking')}</h2>
<p className="mb-8">
{t(
'VEGA token holders can nominate a validator node and receive staking rewards.'
@ -111,9 +115,9 @@ const Home = ({ name }: RouteChildProps) => {
</p>
</HomeSection>
</div>
<div style={{ flex: 1 }}>
<div className="flex-1">
<HomeSection>
<h2 className="text-h4">{t('Governance')}</h2>
<h2 className="text-h4 text-white">{t('Governance')}</h2>
<p className="mb-8">
{t(
'VEGA token holders can vote on proposed changes to the network and create proposals.'

View File

@ -43,20 +43,22 @@ export const TokenDetails = ({
{t('Token address').toUpperCase()}
<Link
data-testid="token-address"
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono"
href={`${ETHERSCAN_URL}/address/${ADDRESSES.vegaTokenAddress}`}
target="_blank"
>
{ADDRESSES.vegaTokenAddress}
</Link>
</KeyValueTableRow>
<KeyValueTableRow>
{t('Vesting contract'.toUpperCase())}
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono"
href={`${ETHERSCAN_URL}/address/${ADDRESSES.vestingAddress}`}
target="_blank"
>
{ADDRESSES.vestingAddress}
</Link>

View File

@ -13,9 +13,9 @@ import { Tranche0Table, TrancheTable } from '../tranche-table';
import { VestingTable } from './vesting-table';
export const RedemptionInformation = () => {
const { state, address } = useOutletContext<{
const { state, account } = useOutletContext<{
state: RedemptionState;
address: string;
account: string;
}>();
const navigate = useNavigate();
const { t } = useTranslation();
@ -28,7 +28,9 @@ export const RedemptionInformation = () => {
trancheBalances,
},
} = useAppState();
const { userTranches } = state;
const filteredTranches = React.useMemo(
() =>
userTranches.filter((tr) => {
@ -41,6 +43,7 @@ export const RedemptionInformation = () => {
}),
[trancheBalances, userTranches]
);
const zeroTranche = React.useMemo(() => {
const zeroTranche = trancheBalances.find((t) => t.id === 0);
if (zeroTranche && zeroTranche.locked.isGreaterThan(0)) {
@ -56,7 +59,9 @@ export const RedemptionInformation = () => {
<Trans
i18nKey="noVestingTokens"
components={{
tranchesLink: <Link to={Routes.TRANCHES} />,
tranchesLink: (
<Link className="underline text-white" to={Routes.TRANCHES} />
),
}}
/>
</p>
@ -67,25 +72,29 @@ export const RedemptionInformation = () => {
return (
<section data-testid="redemption-page">
<Callout>
<div className="mb-12">
<AddLockedTokenAddress />
</Callout>
<p className="mb-12" data-testid="redemption-description">
</div>
<p className="mb-24" data-testid="redemption-description">
{t(
'{{address}} has {{balance}} VEGA tokens in {{tranches}} tranches of the vesting contract.',
{
address: truncateMiddle(address),
address: truncateMiddle(account),
balance: formatNumber(balanceFormatted),
tranches: filteredTranches.length,
}
)}
</p>
<VestingTable
associated={lien}
locked={totalLockedBalance}
vested={totalVestedBalance}
/>
{filteredTranches.length ? <h2>{t('Tranche breakdown')}</h2> : null}
<div className="mb-24">
<VestingTable
associated={lien}
locked={totalLockedBalance}
vested={totalVestedBalance}
/>
</div>
{filteredTranches.length ? (
<h2 className="text-h4 text-white mb-12">{t('Tranche breakdown')}</h2>
) : null}
{zeroTranche && (
<Tranche0Table
trancheId={0}
@ -124,8 +133,10 @@ export const RedemptionInformation = () => {
iconName="hand-up"
intent={Intent.Prompt}
>
<p>{t('Find out more about Staking.')}</p>
<Link to="/staking">{t('Stake VEGA tokens')}</Link>
<p className="mb-12">{t('Find out more about Staking.')}</p>
<Link to="/staking" className="underline text-white">
{t('Stake VEGA tokens')}
</Link>
</Callout>
</section>
);

View File

@ -4,11 +4,13 @@ import { Heading } from '../../components/heading';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '..';
import RedemptionRouter from './redemption';
import { useMatch } from 'react-router-dom';
import { Routes } from '../router-config';
const RedemptionIndex = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const tranche = ':id';
const tranche = useMatch(`${Routes.VESTING}/:id`);
return (
<>

View File

@ -70,7 +70,7 @@ const RedemptionRouter = () => {
if (!account) {
return (
<EthConnectPrompt>
<p data-testid="eth-connect-prompt">
<p data-testid="eth-connect-prompt" className="mb-8">
{t(
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas."
)}

View File

@ -74,11 +74,16 @@ export const RedeemFromTranche = () => {
) {
return (
<section data-testid="redemption-page">
<div data-testid="redemption-no-balance">
{t(
'You do not have any vesting VEGA tokens. Switch to another Ethereum key to check what can be redeemed.'
)}
</div>
<p data-testid="redemption-no-balance">
<Trans
i18nKey="noVestingTokens"
components={{
tranchesLink: (
<Link className="underline text-white" to={Routes.TRANCHES} />
),
}}
/>
</p>
</section>
);
}
@ -92,9 +97,9 @@ export const RedeemFromTranche = () => {
{t('Tokens from this Tranche have been redeemed')}
</strong>
}
completeFooter={
completeBody={
<>
<p>
<p className="mb-8">
{t(
'You have redeemed {{redeemedAmount}} VEGA tokens from this tranche. They are now free to transfer from your Ethereum wallet.',
{
@ -102,7 +107,7 @@ export const RedeemFromTranche = () => {
}
)}
</p>
<p>
<p className="mb-8">
{t(
'The VEGA token address is {{address}}, make sure you add this to your wallet to see your tokens',
{
@ -114,8 +119,18 @@ export const RedeemFromTranche = () => {
<Trans
i18nKey="Go to <stakingLink>staking</stakingLink> or <governanceLink>governance</governanceLink> to see how you can use your unlocked tokens"
components={{
stakingLink: <Link to={Routes.STAKING} />,
governanceLink: <Link to={Routes.GOVERNANCE} />,
stakingLink: (
<Link
className="underline text-white"
to={Routes.STAKING}
/>
),
governanceLink: (
<Link
className="underline text-white"
to={Routes.GOVERNANCE}
/>
),
}}
/>
</p>

View File

@ -112,8 +112,8 @@ export const RewardTable = ({ reward, delegations }: RewardTableProps) => {
}, [delegations, reward.epoch]);
return (
<div>
<h3>
<div className="mb-24">
<h3 className="text-h5 text-white mb-4">
{t('Epoch')} {reward.epoch.id}
</h3>
<KeyValueTable>

View File

@ -6,11 +6,14 @@ export const AssociateInfo = ({ pubKey }: { pubKey: string | null }) => {
const { t } = useTranslation();
return (
<>
<h2 data-testid="associate-vega-key-header">
{t('What Vega wallet/key is going to control your stake?')}
<h2
className="text-h4 text-white"
data-testid="associate-vega-key-header"
>
{t('What Vega key is going to control your stake?')}
</h2>
<ConnectedVegaKey pubKey={pubKey} />
<h2 data-testid="associate-amount-header">
<h2 className="text-h4 text-white" data-testid="associate-amount-header">
{t('How much would you like to associate?')}
</h2>
</>

View File

@ -73,6 +73,7 @@ export const AssociatePage = ({
setSelectedStakingMethod(params.method);
}
}, [params.method, zeroVega, zeroVesting]);
if (txState.txState !== TxState.Default) {
return (
<AssociateTransaction

View File

@ -1,4 +1,10 @@
import { Button, Callout, Link, Intent } from '@vegaprotocol/ui-toolkit';
import {
Button,
Callout,
Link,
Intent,
Loader,
} from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -15,6 +21,7 @@ import {
} from '../../../hooks/transaction-reducer';
import { Routes } from '../../router-config';
import type { PartyStakeLinkings_party_stake_linkings } from './__generated__/PartyStakeLinkings';
import { truncateMiddle } from '../../../lib/truncate-middle';
export const AssociateTransaction = ({
amount,
@ -60,22 +67,27 @@ export const AssociateTransaction = ({
if (derivedTxState === TxState.Pending) {
return (
<Callout intent={Intent.Progress} title={title}>
<p data-testid="transaction-pending-body">
<Callout
icon={<Loader size="small" />}
intent={Intent.Progress}
title={title}
>
<p data-testid="transaction-pending-body" className="mb-8">
{t('Associating {{amount}} VEGA tokens with Vega key {{vegaKey}}', {
amount,
vegaKey,
vegaKey: truncateMiddle(vegaKey),
})}
</p>
<p>
<p className="mb-8">
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${state.txData.hash}`}
target="_blank"
>
{state.txData.hash}
{t('View on Etherscan (opens in a new tab)')}
</Link>
</p>
<p data-testid="transaction-pending-footer">
<p data-testid="transaction-pending-footer" className="mb-8">
{t('pendingAssociationText', {
confirmations: requiredConfirmations,
})}
@ -87,10 +99,9 @@ export const AssociateTransaction = ({
return (
<TransactionCallout
completeHeading={t('Done')}
completeBody={t(
'Vega key {{vegaKey}} can now participate in governance and Nominate a validator with its stake.',
{ vegaKey }
)}
completeBody={t('successfullAssociationMessage', {
vegaKey: truncateMiddle(vegaKey),
})}
completeFooter={
<RouteLink to={Routes.STAKING}>
<Button className="fill">

View File

@ -63,11 +63,11 @@ export const DisassociatePage = ({
'Any Tokens that have been nominated to a node will sacrifice any Rewards they are due for the current epoch. If you do not wish to sacrifices fees you should remove stake from a node at the end of an epoch before disassocation.'
)}
</p>
<h2 className="text-h4 mb-8">
<h2 className="text-h4 text-white mb-8">
{t('What Vega wallet are you removing Tokens from?')}
</h2>
<ConnectedVegaKey pubKey={vegaKey.pub} />
<h2 className="text-h4 mb-8">
<h2 className="text-h4 text-white mb-8">
{t('What tokens would you like to return?')}
</h2>
<StakingMethodRadio

View File

@ -46,7 +46,7 @@ export const NODES_QUERY = gql`
`;
const NodeListItemName = ({ children }: { children: React.ReactNode }) => (
<span className="mr-4 underline">{children}</span>
<span className="mr-4 underline text-white">{children}</span>
);
const NodeListTr = ({ children }: { children: React.ReactNode }) => (

View File

@ -1,6 +1,6 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { Button, Callout, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { BigNumber } from '../../lib/bignumber';
@ -65,7 +65,7 @@ export const PendingStake = ({
} else if (formState === FormState.Pending) {
return (
<Callout
iconName="refresh"
icon={<Loader size="small" />}
title={t('removingPendingStake', { pendingAmount })}
/>
);

View File

@ -1,4 +1,4 @@
import { Callout } from '@vegaprotocol/ui-toolkit';
import { Callout, Loader } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Actions } from './staking-form';
import type { StakeAction } from './staking-form';
@ -22,7 +22,7 @@ export const StakePending = ({
: t('stakeRemovePendingTitle', titleArgs);
return (
<Callout iconName="refresh" title={title}>
<Callout icon={<Loader size="small" />} title={title}>
<p>{t('timeForConfirmation')}</p>
</Callout>
);

View File

@ -92,9 +92,11 @@ export const StakingForm = ({
React.useEffect(() => {
setAmount('');
}, [action, setAmount]);
const { data } = useNetworkParam([
NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT,
]);
const minTokensWithDecimals = React.useMemo(() => {
const minTokens = new BigNumber(data && data.length === 1 ? data[0] : '');
return addDecimal(minTokens, appState.decimals);
@ -198,9 +200,9 @@ export const StakingForm = ({
availableStakeToRemove.isEqualTo(0)
) {
if (appState.lien.isGreaterThan(0)) {
return <span className={'text-red'}>{t('stakeNodeWrongVegaKey')}</span>;
return <span className="text-red">{t('stakeNodeWrongVegaKey')}</span>;
} else {
return <span className={'text-red'}>{t('stakeNodeNone')}</span>;
return <span className="text-red">{t('stakeNodeNone')}</span>;
}
}

View File

@ -77,9 +77,10 @@ export const StakingNode = ({ vegaKey, data }: StakingNodeProps) => {
}, [currentEpoch, data?.party?.delegations]);
const unstaked = React.useMemo(() => {
return new BigNumber(
const value = new BigNumber(
data?.party?.stake.currentStakeAvailableFormatted || 0
).minus(currentDelegationAmount);
return value.isLessThan(0) ? new BigNumber(0) : value;
}, [
currentDelegationAmount,
data?.party?.stake.currentStakeAvailableFormatted,

View File

@ -30,20 +30,22 @@ export const Staking = ({ data }: { data?: StakingQueryResult }) => {
<p className="mb-12">{t('stakingDescription3')}</p>
<p className="mb-12">{t('stakingDescription4')}</p>
<p className="mb-12">
<Link href={Links.STAKING_GUIDE} target="_blank">
<Link
href={Links.STAKING_GUIDE}
className="text-white underline"
target="_blank"
>
{t('readMoreStaking')}
</Link>
</p>
</section>
<section>
<BulletHeader tag="h2" style={{ marginTop: 0 }}>
{t('stakingStep1')}
</BulletHeader>
<BulletHeader tag="h2">{t('stakingStep1')}</BulletHeader>
<StakingStepConnectWallets />
</section>
<section>
<BulletHeader tag="h2">{t('stakingStep2')}</BulletHeader>
<BulletHeader tag="h2">{t('stakingStep1')}</BulletHeader>
<StakingStepAssociate
associated={
new BigNumber(
@ -73,10 +75,11 @@ export const StakingStepConnectWallets = () => {
<p>
{t('Connected Ethereum address')}&nbsp;
<Link
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/tx/${account}`}
target="_blank"
>
{account}
{truncateMiddle(account)}
</Link>
</p>
<p>
@ -90,13 +93,16 @@ export const StakingStepConnectWallets = () => {
return (
<>
<p>
<p className="mb-8">
<Trans
i18nKey="stakingStep1Text"
components={{
vegaWalletLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<Link href={Links.WALLET_GUIDE} target="_blank" />
<Link
href={Links.WALLET_GUIDE}
className="text-white underline"
target="_blank"
/>
),
}}
/>
@ -110,7 +116,7 @@ export const StakingStepConnectWallets = () => {
/>
</div>
) : (
<p>
<p className="mb-8">
<Button
onClick={() =>
appDispatch({
@ -172,18 +178,18 @@ export const StakingStepAssociate = ({
iconName="tick"
title={t('stakingHasAssociated', { tokens: formatNumber(associated) })}
>
<p>
<RouteLink to="/staking/associate">
<div className="flex flex-wrap gap-4">
<RouteLink to="associate">
<Button data-testid="associate-more-tokens-btn">
{t('stakingAssociateMoreButton')}
</Button>
</RouteLink>
</p>
<RouteLink to="/staking/disassociate">
<Button data-testid="disassociate-tokens-btn">
{t('stakingDisassociateButton')}
</Button>
</RouteLink>
<RouteLink to="disassociate">
<Button data-testid="disassociate-tokens-btn">
{t('stakingDisassociateButton')}
</Button>
</RouteLink>
</div>
</Callout>
);
}

View File

@ -59,8 +59,9 @@ export const ValidatorTable = ({
<span>{t('ETHEREUM ADDRESS')}</span>
<span>
<Link
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${node.ethereumAdddress}`}
target="_blank"
>
{node.ethereumAdddress}
</Link>

View File

@ -85,8 +85,9 @@ export const Tranche = () => {
return (
<li className="pb-4" key={i}>
<Link
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/tx/${user.address}`}
target="_blank"
>
{user.address}
</Link>

View File

@ -22,7 +22,7 @@ const Withdraw = () => {
return (
<>
<Heading title={t('withdrawPageHeading')} />
<p>{t('withdrawPageText')}</p>
<p className="mb-8">{t('withdrawPageText')}</p>
<div className="mb-24">
<VegaWalletContainer>
{(currVegaKey) => <WithdrawContainer currVegaKey={currVegaKey} />}
@ -140,9 +140,9 @@ export const WithdrawContainer = ({ currVegaKey }: WithdrawContainerProps) => {
title={t('pendingWithdrawalsCalloutTitle')}
intent={Intent.Prompt}
>
<p>{t('pendingWithdrawalsCalloutText')}</p>
<p className="mb-0">
<Link to={Routes.WITHDRAWALS}>
<p className="mb-8">{t('pendingWithdrawalsCalloutText')}</p>
<p>
<Link to={Routes.WITHDRAWALS} className="underline text-white">
{t('pendingWithdrawalsCalloutButton')}
</Link>
</p>

View File

@ -1,4 +1,4 @@
import { Splash } from '@vegaprotocol/ui-toolkit';
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { format } from 'date-fns';
import orderBy from 'lodash/orderBy';
import React from 'react';
@ -10,7 +10,6 @@ import { Heading } from '../../components/heading';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { SplashLoader } from '../../components/splash-loader';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { BigNumber } from '../../lib/bignumber';
import { DATE_FORMAT_DETAILED } from '../../lib/date-formats';
import { addDecimal } from '../../lib/decimals';
@ -27,21 +26,13 @@ const Withdrawals = () => {
<>
<Heading title={t('withdrawalsTitle')} />
<VegaWalletContainer>
{(currVegaKey) => (
<WithdrawPendingContainer currVegaKey={currVegaKey} />
)}
{(currVegaKey) => <WithdrawPendingContainer />}
</VegaWalletContainer>
</>
);
};
interface WithdrawPendingContainerProps {
currVegaKey: VegaKeyExtended;
}
const WithdrawPendingContainer = ({
currVegaKey,
}: WithdrawPendingContainerProps) => {
const WithdrawPendingContainer = () => {
const { t } = useTranslation();
const { transaction, submit } = useCompleteWithdraw();
const { data, loading, error } = useWithdrawals();
@ -80,11 +71,11 @@ const WithdrawPendingContainer = ({
return (
<>
<h2>{t('withdrawalsPreparedWarningHeading')}</h2>
<p>{t('withdrawalsText')}</p>
<p>{t('withdrawalsPreparedWarningText')}</p>
<p className="mb-8">{t('withdrawalsText')}</p>
<p className="mb-28">{t('withdrawalsPreparedWarningText')}</p>
<ul role="list">
{withdrawals.map((w) => (
<li key={w.id}>
<li key={w.id} className="mb-28">
<Withdrawal withdrawal={w} complete={submit} />
</li>
))}
@ -102,37 +93,34 @@ interface WithdrawalProps {
export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
let status = null;
let footer = null;
const renderStatus = ({
id,
status,
txHash,
pendingOnForeignChain,
}: Withdrawals_party_withdrawals) => {
if (pendingOnForeignChain) {
return t('Pending');
if (withdrawal.pendingOnForeignChain) {
status = t('Pending');
footer = (
<Button
className="w-full"
disabled={true}
onClick={() => complete(withdrawal.id)}
>
{t('withdrawalsCompleteButton')}
</Button>
);
} else if (withdrawal.status === WithdrawalStatus.Finalized) {
if (withdrawal.txHash) {
status = t('Complete');
} else {
status = t('Incomplete');
footer = (
<Button className="w-full" onClick={() => complete(withdrawal.id)}>
{t('withdrawalsCompleteButton')}
</Button>
);
}
if (status === WithdrawalStatus.Finalized) {
if (txHash) {
return t('Complete');
} else {
return (
<>
{t('Incomplete')}{' '}
<button
className="text-white underline"
onClick={() => complete(id)}
>
{t('withdrawalsCompleteButton')}
</button>
</>
);
}
}
return status;
};
} else {
status = withdrawal.status;
}
return (
<div>
@ -151,8 +139,9 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
{t('toEthereum')}
<span>
<Link
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/tx/${withdrawal.details?.receiverAddress}`}
target="_blank"
>
{truncateMiddle(withdrawal.details?.receiverAddress ?? '')}
</Link>
@ -174,6 +163,7 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${withdrawal.txHash}`}
target="_blank"
>
{truncateMiddle(withdrawal.txHash)}
</Link>
@ -184,9 +174,10 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
</KeyValueTableRow>
<KeyValueTableRow>
{t('status')}
{renderStatus(withdrawal)}
{status}
</KeyValueTableRow>
</KeyValueTable>
{footer}
</div>
);
};

View File

@ -45,7 +45,7 @@ const getIconElement = ({
CalloutProps,
'icon' | 'iconName' | 'iconDescription' | 'isLoading'
>) => {
const wrapperClassName = 'ml-8 mr-16 mt-8';
const wrapperClassName = 'mt-4';
if (isLoading) {
return (
<div className={wrapperClassName}>
@ -64,7 +64,10 @@ const getIconElement = ({
/>
);
}
return <div className={wrapperClassName}>{icon}</div>;
if (icon) {
return <div className={wrapperClassName}>{icon}</div>;
}
return null;
};
export function Callout({
@ -85,30 +88,30 @@ export function Callout({
});
const className = classNames(
'flex gap-20',
'border',
'border-black',
'dark:border-white',
'text-body-large',
'dark:text-white',
'p-16',
getIntentShadow(intent),
{
flex: iconElement,
}
getIntentShadow(intent)
);
const TitleTag: keyof JSX.IntrinsicElements = headingLevel
? `h${headingLevel}`
: 'div';
const body = (
<>
{title && <TitleTag className="text-h5 mt-0 mb-8">{title}</TitleTag>}
{title && (
<TitleTag className="text-h5 mt-0 mb-8 last:mb-0">{title}</TitleTag>
)}
{children}
</>
);
return (
<div data-testid="callout" className={className}>
{iconElement}
{iconElement ? <div className="grow">{body}</div> : body}
<div className="grow">{body}</div>
</div>
);
}

View File

@ -190,7 +190,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
if (element) {
return (
<div className="inline-flex items-center relative">
<div className="flex items-center relative">
{hasPrepended && element}
{input}
{hasAppended && element}

View File

@ -10,11 +10,6 @@ const Template: Story = (args) => <Lozenge {...args}>lozenge</Lozenge>;
export const Default = Template.bind({});
export const WithDetails = Template.bind({});
WithDetails.args = {
details: 'details text',
};
export const Highlight = Template.bind({});
Highlight.args = {
variant: 'highlight',

View File

@ -1,38 +1,31 @@
import type { ReactNode } from 'react';
import classNames from 'classnames';
import { getVariantBackground } from '../../utils/intent';
import type { TailwindIntents } from '../../utils/intent';
import { TailwindIntents } from '../../utils/intent';
interface LozengeProps {
children: ReactNode;
variant?: TailwindIntents;
className?: string;
details?: string;
}
const getWrapperClasses = (className: LozengeProps['className']) => {
return classNames('inline-flex items-center gap-4', className);
};
const getLozengeClasses = (variant: LozengeProps['variant']) => {
const getLozengeClasses = (
variant: LozengeProps['variant'],
className?: string
) => {
return classNames(
['rounded-md', 'font-mono', 'leading-none', 'p-4'],
getVariantBackground(variant)
getVariantBackground(variant),
className
);
};
export const Lozenge = ({
children,
variant,
variant = TailwindIntents.Highlight,
className,
details,
...props
}: LozengeProps) => {
return (
<span className={getWrapperClasses(className)} {...props}>
<span className={getLozengeClasses(variant)}>{children}</span>
{details && <span>{details}</span>}
</span>
<span className={getLozengeClasses(variant, className)}>{children}</span>
);
};

View File

@ -27,18 +27,18 @@ export const Web3ConnectDialog = ({
>
<ul data-testid="web3-connector-list">
{connectors.map(([connector], i) => {
const connectorName = getConnectorName(connector);
const info = getConnectorInfo(connector);
return (
<li key={i} className="mb-12 last:mb-0">
<button
className="capitalize hover:text-vega-pink dark:hover:text-vega-yellow underline"
data-testid={`web3-connector-${connectorName}`}
className="hover:text-vega-pink dark:hover:text-vega-yellow underline"
data-testid={`web3-connector-${info.name}`}
onClick={async () => {
await connector.activate(desiredChainId);
setDialogOpen(false);
}}
>
{connectorName}
{info.text}
</button>
</li>
);
@ -48,8 +48,16 @@ export const Web3ConnectDialog = ({
);
};
function getConnectorName(connector: Connector) {
if (connector instanceof MetaMask) return 'MetaMask';
if (connector instanceof WalletConnect) return 'WalletConnect';
return 'Unknown';
function getConnectorInfo(connector: Connector) {
if (connector instanceof MetaMask)
return {
name: 'MetaMask',
text: t('MetaMask, Brave or other injected web wallet'),
};
if (connector instanceof WalletConnect)
return {
name: 'WalletConnect',
text: t('WalletConnect'),
};
return { name: 'Unknown', text: t('Unknown') };
}

View File

@ -138,7 +138,7 @@ const RecipientCell = ({
}: RecipientCellProps) => {
return (
<Link
title={t('View address on Etherscan')}
title={t('View on Etherscan (opens in a new tab)')}
href={`${ethUrl}/address/${value}`}
data-testid="etherscan-link"
>