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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ interface BulletHeaderProps {
export const BulletHeader = ({ tag, children, style }: BulletHeaderProps) => { export const BulletHeader = ({ tag, children, style }: BulletHeaderProps) => {
return React.createElement( return React.createElement(
tag, 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" /> <span className="inline-block w-[12px] h-[12px] mr-12 bg-white" />
{children} {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> <WalletCardActions>
<Link style={{ flex: 1 }} to={`${Routes.STAKING}/associate`}> <Link className="flex-1" to={`${Routes.STAKING}/associate`}>
<Button variant="primary" className="w-full"> <span className="flex items-center justify-center w-full text-center px-28 border h-28">
{t('associate')} {t('associate')}
</Button> </span>
</Link> </Link>
<Link style={{ flex: 1 }} to={`${Routes.STAKING}/disassociate`}> <Link className="flex-1" to={`${Routes.STAKING}/disassociate`}>
<Button variant="primary" className="w-full"> <span className="flex items-center justify-center w-full px-28 border h-28">
{t('disassociate')} {t('disassociate')}
</Button> </span>
</Link> </Link>
</WalletCardActions> </WalletCardActions>
</> </>
@ -187,7 +187,7 @@ export const EthWallet = () => {
return ( return (
<WalletCard> <WalletCard>
<WalletCardHeader> <WalletCardHeader>
<h1 className="text-h3 px-8 uppercase">{t('ethereumKey')}</h1> <h1 className="text-h3 uppercase">{t('ethereumKey')}</h1>
{account && ( {account && (
<div className="font-mono px-4 text-right"> <div className="font-mono px-4 text-right">
<div>{truncateMiddle(account)}</div> <div>{truncateMiddle(account)}</div>
@ -215,8 +215,8 @@ export const EthWallet = () => {
{account ? ( {account ? (
<ConnectedKey /> <ConnectedKey />
) : ( ) : (
<Button <button
className="fill button-secondary--inverted" className="w-full px-28 border h-28"
onClick={() => onClick={() =>
appDispatch({ appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY, type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
@ -226,7 +226,7 @@ export const EthWallet = () => {
data-test-id="connect-to-eth-wallet-button" data-test-id="connect-to-eth-wallet-button"
> >
{t('connectEthWalletToAssociate')} {t('connectEthWalletToAssociate')}
</Button> </button>
)} )}
{account && ( {account && (
<WalletCardActions> <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 ( return (
<header className="my-0 mx-auto"> <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} {title}
</h1> </h1>
</header> </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 <div
className={`flex justify-between py-2 font-mono ${ 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} {children}

View File

@ -5,14 +5,13 @@ import debounce from 'lodash/debounce';
import React from 'react'; import React from 'react';
import * as Dialog from '@radix-ui/react-dialog'; import * as Dialog from '@radix-ui/react-dialog';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { Flags } from '../../config'; import { Flags } from '../../config';
import { import {
AppStateActionType, AppStateActionType,
useAppState, useAppState,
} from '../../contexts/app-state/app-state-context'; } from '../../contexts/app-state/app-state-context';
import vegaWhite from '../../images/vega_white.png';
import { Routes } from '../../routes/router-config'; import { Routes } from '../../routes/router-config';
import { EthWallet } from '../eth-wallet'; import { EthWallet } from '../eth-wallet';
import { VegaWallet } from '../vega-wallet'; import { VegaWallet } from '../vega-wallet';
@ -36,14 +35,14 @@ export const Nav = () => {
return ( return (
<div <div
className={`p-8 ${ className={`p-16 ${
inverted inverted
? 'bg-clouds bg-no-repeat bg-cover bg-vega-yellow' ? 'bg-clouds bg-no-repeat bg-cover bg-vega-yellow'
: 'border-b-white border-b-1' : 'border-b-white border-b-1'
}`} }`}
> >
{isDesktop && <NavHeader fairground={inverted} />} {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} />} {!isDesktop && <NavHeader fairground={inverted} />}
<div className="flex gap-12 lg:flex-auto"> <div className="flex gap-12 lg:flex-auto">
{isDesktop ? ( {isDesktop ? (
@ -61,76 +60,10 @@ const NavHeader = ({ fairground }: { fairground: boolean }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="h-[30px] inline-flex items-center ml-8 lg:h-40 uppercase"> <div className="h-[30px] inline-flex items-center 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>
<h1 <h1
data-testid="header-title" 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' 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]"> <div className="border-b border-white lg:grid lg:grid-rows-[auto_minmax(600px,_1fr)] lg:grid-cols-[1fr_450px]">
<Nav /> <Nav />
<main className="p-20">{children}</main> <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) => ( {sidebar.map((Component, i) => (
<section className="mb-20 last:mb-0" key={i}> <section className="mb-20 last:mb-0" key={i}>
{Component} {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, Input,
Intent, Intent,
FormGroup, FormGroup,
Lozenge,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -25,7 +26,6 @@ export const AmountInput = ({
amount, amount,
setAmount, setAmount,
maximum, maximum,
// TODO: render currency in input when https://github.com/vegaprotocol/frontend-monorepo/issues/273
currency, currency,
}: { }: {
amount: string; amount: string;
@ -36,25 +36,31 @@ export const AmountInput = ({
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="flex"> <div className="flex">
<Input <div className="flex-1">
data-testid="token-amount-input" <Input
className="flex-1" data-testid="token-amount-input"
name={inputName} name={inputName}
id={inputName} id={inputName}
onChange={(e) => setAmount(e.target.value)} onChange={(e) => setAmount(e.target.value)}
value={amount} value={amount}
autoComplete="off" autoComplete="off"
type="number" type="number"
max={maximum.toString()} max={maximum.toString()}
min={0} min={0}
step="any" step="any"
/> appendElement={
<Lozenge className="text-[10px] relative top-[-2px]">
{currency}
</Lozenge>
}
/>
</div>
{maximum && ( {maximum && (
<Button <Button
variant="inline-link" variant="inline-link"
onClick={() => setAmount(maximum.toString())} onClick={() => setAmount(maximum.toString())}
data-testid="token-amount-use-maximum" 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')} {t('Use maximum')}
</Button> </Button>
@ -69,7 +75,6 @@ export const TokenInput = ({
perform, perform,
submitText, submitText,
currency, currency,
approveText, approveText,
allowance, allowance,
approve, approve,
@ -84,7 +89,6 @@ export const TokenInput = ({
perform: () => void; perform: () => void;
submitText: string; submitText: string;
currency: string; currency: string;
requireApproval?: boolean; requireApproval?: boolean;
maximum?: BigNumber; maximum?: BigNumber;
minimum?: BigNumber; minimum?: BigNumber;
@ -128,6 +132,7 @@ export const TokenInput = ({
new BigNumber(amount).isLessThan(minimum) new BigNumber(amount).isLessThan(minimum)
); );
}, [amount, isApproved, maximum, requireApproval, minimum]); }, [amount, isApproved, maximum, requireApproval, minimum]);
let approveContent = null; let approveContent = null;
if (showApproveButton) { if (showApproveButton) {
@ -158,11 +163,9 @@ export const TokenInput = ({
} }
} else if (requireApproval) { } else if (requireApproval) {
approveContent = ( approveContent = (
<Callout <Callout iconName="tick" intent={Intent.Success}>
iconName="tick" <p>{`${currency} are approved for staking`}</p>
intent={Intent.Success} </Callout>
title={`${currency} are approved for staking`}
/>
); );
} }

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} intent={Intent.Success}
title={heading || t('Complete')} title={heading || t('Complete')}
> >
{body && <p data-testid="transaction-complete-body">{body}</p>} {body && (
<p> <p className="mb-8" data-testid="transaction-complete-body">
{body}
</p>
)}
<p className="mb-8">
<Link <Link
title={t('View transaction on Etherscan')} title={t('View transaction on Etherscan')}
target="_blank"
href={`${ETHERSCAN_URL}/tx/${hash}`} href={`${ETHERSCAN_URL}/tx/${hash}`}
> >
{hash} {t('View transaction on Etherscan')}
</Link> </Link>
</p> </p>
{footer && <p data-testid="transaction-complete-footer">{footer}</p>} {footer && <p data-testid="transaction-complete-footer">{footer}</p>}

View File

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

View File

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

View File

@ -7,7 +7,7 @@ export const TransactionRequested = () => {
<Callout <Callout
iconName="hand-up" iconName="hand-up"
intent={Intent.Prompt} 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 <Link
title={t('View transaction on Etherscan')} title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`} href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`}
target="_blank"
> >
{truncateMiddle(transaction.tx.hash)} {truncateMiddle(transaction.tx.hash)}
</Link> </Link>

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -27,15 +28,13 @@ interface WalletCardProps {
dark?: boolean; dark?: boolean;
} }
export const WalletCard = ({ dark, children }: WalletCardProps) => ( export const WalletCard = ({ dark, children }: WalletCardProps) => {
<div const className = classNames('text-ui border border-white', 'p-8', {
className={`text-ui border border-white ${ 'bg-black text-white': dark,
dark ? 'bg-black text-white' : 'bg-white text-black' 'bg-white text-black': !dark,
}`} });
> return <div className={className}>{children}</div>;
{children} };
</div>
);
interface WalletCardHeaderProps { interface WalletCardHeaderProps {
children: React.ReactNode; children: React.ReactNode;
@ -44,7 +43,7 @@ interface WalletCardHeaderProps {
export const WalletCardHeader = ({ children }: WalletCardHeaderProps) => { export const WalletCardHeader = ({ children }: WalletCardHeaderProps) => {
return ( 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) => { export const WalletCardContent = ({ children }: WalletCardContentProps) => {
return <div className="my-4 mx-8">{children}</div>; return <div className="mt-8">{children}</div>;
}; };
export const WalletCardRow = ({ 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", "Home": "Home",
"fairgroundTitle": "Fairground token", "fairgroundTitle": "Fairground token",
"pageTitleHome": "$VEGA Tokens", "pageTitleHome": "The $VEGA token",
"pageTitleClaim": "Claim $VEGA tokens", "pageTitleClaim": "Claim $VEGA tokens",
"pageTitleAssociate": "Associate $VEGA tokens with $VEGA Key", "pageTitleAssociate": "Associate $VEGA tokens with Vega Key",
"pageTitleRedemption": "Vesting", "pageTitleRedemption": "Vesting",
"pageTitleLiquidity": "Incentivised Liquidity Programme", "pageTitleLiquidity": "Incentivised Liquidity Programme",
"pageTitleRedemptionTranche": "Redeem from Tranche", "pageTitleRedemptionTranche": "Redeem from Tranche",
@ -79,6 +79,7 @@
"Incomplete": "Incomplete", "Incomplete": "Incomplete",
"Complete": "Complete", "Complete": "Complete",
"View on Etherscan (opens in a new tab)": "View on Etherscan (opens in a new tab)", "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", "Transaction in progress": "Transaction in progress",
"Unknown error": "Unknown error", "Unknown error": "Unknown error",
"Awaiting action in Ethereum wallet (e.g. MetaMask)": "Awaiting action in Ethereum wallet (e.g. MetaMask)", "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>", "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", "ethereumKey": "Ethereum key",
"checkingForProvider": "Checking for provider", "checkingForProvider": "Checking for provider",
"Awaiting action in wallet...": "Awaiting action in Ethereum wallet (e.g. MetaMask)",
"In wallet": "In wallet", "In wallet": "In wallet",
"Not staked": "Not staked", "Not staked": "Not staked",
"viewKeys": "View keys", "viewKeys": "View keys",
@ -149,7 +149,6 @@
"Stake VEGA tokens": "Stake $VEGA tokens", "Stake VEGA tokens": "Stake $VEGA tokens",
"Tranche breakdown": "Tranche breakdown", "Tranche breakdown": "Tranche breakdown",
"Across all tranches": "Across all tranches", "Across all tranches": "Across all tranches",
"theVegaToken": "The {{symbol}} token",
"Token Vesting": "Vesting", "Token Vesting": "Vesting",
"Rewards": "Rewards", "Rewards": "Rewards",
"Governance": "Governance", "Governance": "Governance",
@ -238,7 +237,7 @@
"VEGA Tokens": "$VEGA Tokens", "VEGA Tokens": "$VEGA Tokens",
"SLP Tokens": "SLP Tokens", "SLP Tokens": "SLP Tokens",
"Connected Vega key": "Connected Vega key", "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?", "Where would you like to stake from?": "Where would you like to associate from?",
"associateNoVega": "Your connected Ethereum address does not have any $VEGA", "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.", "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", "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 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.", "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", "Check your vesting VEGA tokens": "Check your vesting $VEGA tokens",
"Tokens from this Tranche have been redeemed": "Tokens from this Tranche have been redeemed", "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.", "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", "transaction": "Transaction",
"associated": "Associated", "associated": "Associated",
"notAssociated": "Not 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", "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", "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.", "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", "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", "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", "stakingStep3": "Step 3. Select the validator you'd like to nominate",
"stakeAddSuccessMessage": "You can cancel your nomination at any time", "stakeAddSuccessMessage": "You can cancel your nomination at any time",

View File

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

View File

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

View File

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

View File

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

View File

@ -20,58 +20,67 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
return <p>{t('noProposals')}</p>; 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 ( return (
<> <>
<Heading title={t('pageTitleGovernance')} /> <Heading title={t('pageTitleGovernance')} />
<p>{t('proposedChangesToVegaNetwork')}</p> <p className="mb-8">{t('proposedChangesToVegaNetwork')}</p>
<p>{t('vegaTokenHoldersCanVote')}</p> <p className="mb-8">{t('vegaTokenHoldersCanVote')}</p>
<p>{t('requiredMajorityDescription')}</p> <p className="mb-8">{t('requiredMajorityDescription')}</p>
<h2>{t('proposals')}</h2> <h2 className="text-h4 text-white">{t('proposals')}</h2>
<ul>{proposals.map((row) => renderRow(row))}</ul> <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 * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { ProposalState } from '../../../../__generated__/globalTypes';
ProposalState, import { VoteValue } from '../../../../__generated__/globalTypes';
VoteValue,
} from '../../../../__generated__/globalTypes';
import { import {
AppStateActionType, AppStateActionType,
useAppState, useAppState,
@ -141,9 +139,8 @@ export const VoteButtons = ({
: 'text-intent-danger'; : 'text-intent-danger';
return ( return (
<p> <p>
<span>{t('youVoted')}</span>{' '} <span>{t('youVoted')}:</span>{' '}
<span className={className}>{t(`voteState_${voteState}`)}</span> <span className={className}>{t(`voteState_${voteState}`)}</span>{' '}
{'. '}
{voteDatetime ? ( {voteDatetime ? (
<span>{format(voteDatetime, DATE_FORMAT_LONG)}. </span> <span>{format(voteDatetime, DATE_FORMAT_LONG)}. </span>
) : null} ) : null}
@ -167,17 +164,11 @@ export const VoteButtons = ({
} }
return ( return (
<div className="flex"> <div className="flex gap-4">
<Button <Button onClick={() => submitVote(VoteValue.Yes)} className="flex-1">
onClick={() => submitVote(VoteValue.Yes)}
className="w-[calc(50%_-_7px)] mt-4 mr-12"
>
{t('voteFor')} {t('voteFor')}
</Button> </Button>
<Button <Button onClick={() => submitVote(VoteValue.No)} className="flex-1">
onClick={() => submitVote(VoteValue.No)}
className="w-[calc(50%_-_7px)] mt-4"
>
{t('voteAgainst')} {t('voteAgainst')}
</Button> </Button>
</div> </div>

View File

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

View File

@ -8,21 +8,21 @@ export const VoteProgress = ({
progress: BigNumber; progress: BigNumber;
}) => { }) => {
return ( return (
<> <div className="w-full h-4 relative">
<div <div
data-testid="vote-progress-indicator" 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}%` }} 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 <div
className="bp3-progress-meter bg-vega-green rounded-none" className="absolute left-0 bg-vega-green h-4"
data-testid="vote-progress-bar" data-testid="vote-progress-bar"
style={{ style={{
width: `${progress}%`, width: `${progress}%`,
}} }}
/> />
</div> </div>
</> </div>
); );
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -70,7 +70,7 @@ const RedemptionRouter = () => {
if (!account) { if (!account) {
return ( return (
<EthConnectPrompt> <EthConnectPrompt>
<p data-testid="eth-connect-prompt"> <p data-testid="eth-connect-prompt" className="mb-8">
{t( {t(
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas." "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 ( return (
<section data-testid="redemption-page"> <section data-testid="redemption-page">
<div data-testid="redemption-no-balance"> <p data-testid="redemption-no-balance">
{t( <Trans
'You do not have any vesting VEGA tokens. Switch to another Ethereum key to check what can be redeemed.' i18nKey="noVestingTokens"
)} components={{
</div> tranchesLink: (
<Link className="underline text-white" to={Routes.TRANCHES} />
),
}}
/>
</p>
</section> </section>
); );
} }
@ -92,9 +97,9 @@ export const RedeemFromTranche = () => {
{t('Tokens from this Tranche have been redeemed')} {t('Tokens from this Tranche have been redeemed')}
</strong> </strong>
} }
completeFooter={ completeBody={
<> <>
<p> <p className="mb-8">
{t( {t(
'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.',
{ {
@ -102,7 +107,7 @@ export const RedeemFromTranche = () => {
} }
)} )}
</p> </p>
<p> <p className="mb-8">
{t( {t(
'The VEGA token address is {{address}}, make sure you add this to your wallet to see your tokens', '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 <Trans
i18nKey="Go to <stakingLink>staking</stakingLink> or <governanceLink>governance</governanceLink> to see how you can use your unlocked tokens" i18nKey="Go to <stakingLink>staking</stakingLink> or <governanceLink>governance</governanceLink> to see how you can use your unlocked tokens"
components={{ components={{
stakingLink: <Link to={Routes.STAKING} />, stakingLink: (
governanceLink: <Link to={Routes.GOVERNANCE} />, <Link
className="underline text-white"
to={Routes.STAKING}
/>
),
governanceLink: (
<Link
className="underline text-white"
to={Routes.GOVERNANCE}
/>
),
}} }}
/> />
</p> </p>

View File

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

View File

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

View File

@ -73,6 +73,7 @@ export const AssociatePage = ({
setSelectedStakingMethod(params.method); setSelectedStakingMethod(params.method);
} }
}, [params.method, zeroVega, zeroVesting]); }, [params.method, zeroVega, zeroVesting]);
if (txState.txState !== TxState.Default) { if (txState.txState !== TxState.Default) {
return ( return (
<AssociateTransaction <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 { useEnvironment } from '@vegaprotocol/react-helpers';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -15,6 +21,7 @@ import {
} from '../../../hooks/transaction-reducer'; } from '../../../hooks/transaction-reducer';
import { Routes } from '../../router-config'; import { Routes } from '../../router-config';
import type { PartyStakeLinkings_party_stake_linkings } from './__generated__/PartyStakeLinkings'; import type { PartyStakeLinkings_party_stake_linkings } from './__generated__/PartyStakeLinkings';
import { truncateMiddle } from '../../../lib/truncate-middle';
export const AssociateTransaction = ({ export const AssociateTransaction = ({
amount, amount,
@ -60,22 +67,27 @@ export const AssociateTransaction = ({
if (derivedTxState === TxState.Pending) { if (derivedTxState === TxState.Pending) {
return ( return (
<Callout intent={Intent.Progress} title={title}> <Callout
<p data-testid="transaction-pending-body"> 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}}', { {t('Associating {{amount}} VEGA tokens with Vega key {{vegaKey}}', {
amount, amount,
vegaKey, vegaKey: truncateMiddle(vegaKey),
})} })}
</p> </p>
<p> <p className="mb-8">
<Link <Link
title={t('View transaction on Etherscan')} title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${state.txData.hash}`} href={`${ETHERSCAN_URL}/tx/${state.txData.hash}`}
target="_blank"
> >
{state.txData.hash} {t('View on Etherscan (opens in a new tab)')}
</Link> </Link>
</p> </p>
<p data-testid="transaction-pending-footer"> <p data-testid="transaction-pending-footer" className="mb-8">
{t('pendingAssociationText', { {t('pendingAssociationText', {
confirmations: requiredConfirmations, confirmations: requiredConfirmations,
})} })}
@ -87,10 +99,9 @@ export const AssociateTransaction = ({
return ( return (
<TransactionCallout <TransactionCallout
completeHeading={t('Done')} completeHeading={t('Done')}
completeBody={t( completeBody={t('successfullAssociationMessage', {
'Vega key {{vegaKey}} can now participate in governance and Nominate a validator with its stake.', vegaKey: truncateMiddle(vegaKey),
{ vegaKey } })}
)}
completeFooter={ completeFooter={
<RouteLink to={Routes.STAKING}> <RouteLink to={Routes.STAKING}>
<Button className="fill"> <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.' '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> </p>
<h2 className="text-h4 mb-8"> <h2 className="text-h4 text-white mb-8">
{t('What Vega wallet are you removing Tokens from?')} {t('What Vega wallet are you removing Tokens from?')}
</h2> </h2>
<ConnectedVegaKey pubKey={vegaKey.pub} /> <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?')} {t('What tokens would you like to return?')}
</h2> </h2>
<StakingMethodRadio <StakingMethodRadio

View File

@ -46,7 +46,7 @@ export const NODES_QUERY = gql`
`; `;
const NodeListItemName = ({ children }: { children: React.ReactNode }) => ( 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 }) => ( const NodeListTr = ({ children }: { children: React.ReactNode }) => (

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import * as Sentry from '@sentry/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 { useTranslation } from 'react-i18next';
import { useAppState } from '../../contexts/app-state/app-state-context'; import { useAppState } from '../../contexts/app-state/app-state-context';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
@ -65,7 +65,7 @@ export const PendingStake = ({
} else if (formState === FormState.Pending) { } else if (formState === FormState.Pending) {
return ( return (
<Callout <Callout
iconName="refresh" icon={<Loader size="small" />}
title={t('removingPendingStake', { pendingAmount })} 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 { useTranslation } from 'react-i18next';
import { Actions } from './staking-form'; import { Actions } from './staking-form';
import type { StakeAction } from './staking-form'; import type { StakeAction } from './staking-form';
@ -22,7 +22,7 @@ export const StakePending = ({
: t('stakeRemovePendingTitle', titleArgs); : t('stakeRemovePendingTitle', titleArgs);
return ( return (
<Callout iconName="refresh" title={title}> <Callout icon={<Loader size="small" />} title={title}>
<p>{t('timeForConfirmation')}</p> <p>{t('timeForConfirmation')}</p>
</Callout> </Callout>
); );

View File

@ -92,9 +92,11 @@ export const StakingForm = ({
React.useEffect(() => { React.useEffect(() => {
setAmount(''); setAmount('');
}, [action, setAmount]); }, [action, setAmount]);
const { data } = useNetworkParam([ const { data } = useNetworkParam([
NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT, NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT,
]); ]);
const minTokensWithDecimals = React.useMemo(() => { const minTokensWithDecimals = React.useMemo(() => {
const minTokens = new BigNumber(data && data.length === 1 ? data[0] : ''); const minTokens = new BigNumber(data && data.length === 1 ? data[0] : '');
return addDecimal(minTokens, appState.decimals); return addDecimal(minTokens, appState.decimals);
@ -198,9 +200,9 @@ export const StakingForm = ({
availableStakeToRemove.isEqualTo(0) availableStakeToRemove.isEqualTo(0)
) { ) {
if (appState.lien.isGreaterThan(0)) { if (appState.lien.isGreaterThan(0)) {
return <span className={'text-red'}>{t('stakeNodeWrongVegaKey')}</span>; return <span className="text-red">{t('stakeNodeWrongVegaKey')}</span>;
} else { } 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]); }, [currentEpoch, data?.party?.delegations]);
const unstaked = React.useMemo(() => { const unstaked = React.useMemo(() => {
return new BigNumber( const value = new BigNumber(
data?.party?.stake.currentStakeAvailableFormatted || 0 data?.party?.stake.currentStakeAvailableFormatted || 0
).minus(currentDelegationAmount); ).minus(currentDelegationAmount);
return value.isLessThan(0) ? new BigNumber(0) : value;
}, [ }, [
currentDelegationAmount, currentDelegationAmount,
data?.party?.stake.currentStakeAvailableFormatted, 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('stakingDescription3')}</p>
<p className="mb-12">{t('stakingDescription4')}</p> <p className="mb-12">{t('stakingDescription4')}</p>
<p className="mb-12"> <p className="mb-12">
<Link href={Links.STAKING_GUIDE} target="_blank"> <Link
href={Links.STAKING_GUIDE}
className="text-white underline"
target="_blank"
>
{t('readMoreStaking')} {t('readMoreStaking')}
</Link> </Link>
</p> </p>
</section> </section>
<section> <section>
<BulletHeader tag="h2" style={{ marginTop: 0 }}> <BulletHeader tag="h2">{t('stakingStep1')}</BulletHeader>
{t('stakingStep1')}
</BulletHeader>
<StakingStepConnectWallets /> <StakingStepConnectWallets />
</section> </section>
<section> <section>
<BulletHeader tag="h2">{t('stakingStep2')}</BulletHeader> <BulletHeader tag="h2">{t('stakingStep1')}</BulletHeader>
<StakingStepAssociate <StakingStepAssociate
associated={ associated={
new BigNumber( new BigNumber(
@ -73,10 +75,11 @@ export const StakingStepConnectWallets = () => {
<p> <p>
{t('Connected Ethereum address')}&nbsp; {t('Connected Ethereum address')}&nbsp;
<Link <Link
title={t('View address on Etherscan')} title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/tx/${account}`} href={`${ETHERSCAN_URL}/tx/${account}`}
target="_blank"
> >
{account} {truncateMiddle(account)}
</Link> </Link>
</p> </p>
<p> <p>
@ -90,13 +93,16 @@ export const StakingStepConnectWallets = () => {
return ( return (
<> <>
<p> <p className="mb-8">
<Trans <Trans
i18nKey="stakingStep1Text" i18nKey="stakingStep1Text"
components={{ components={{
vegaWalletLink: ( vegaWalletLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content <Link
<Link href={Links.WALLET_GUIDE} target="_blank" /> href={Links.WALLET_GUIDE}
className="text-white underline"
target="_blank"
/>
), ),
}} }}
/> />
@ -110,7 +116,7 @@ export const StakingStepConnectWallets = () => {
/> />
</div> </div>
) : ( ) : (
<p> <p className="mb-8">
<Button <Button
onClick={() => onClick={() =>
appDispatch({ appDispatch({
@ -172,18 +178,18 @@ export const StakingStepAssociate = ({
iconName="tick" iconName="tick"
title={t('stakingHasAssociated', { tokens: formatNumber(associated) })} title={t('stakingHasAssociated', { tokens: formatNumber(associated) })}
> >
<p> <div className="flex flex-wrap gap-4">
<RouteLink to="/staking/associate"> <RouteLink to="associate">
<Button data-testid="associate-more-tokens-btn"> <Button data-testid="associate-more-tokens-btn">
{t('stakingAssociateMoreButton')} {t('stakingAssociateMoreButton')}
</Button> </Button>
</RouteLink> </RouteLink>
</p> <RouteLink to="disassociate">
<RouteLink to="/staking/disassociate"> <Button data-testid="disassociate-tokens-btn">
<Button data-testid="disassociate-tokens-btn"> {t('stakingDisassociateButton')}
{t('stakingDisassociateButton')} </Button>
</Button> </RouteLink>
</RouteLink> </div>
</Callout> </Callout>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,18 +27,18 @@ export const Web3ConnectDialog = ({
> >
<ul data-testid="web3-connector-list"> <ul data-testid="web3-connector-list">
{connectors.map(([connector], i) => { {connectors.map(([connector], i) => {
const connectorName = getConnectorName(connector); const info = getConnectorInfo(connector);
return ( return (
<li key={i} className="mb-12 last:mb-0"> <li key={i} className="mb-12 last:mb-0">
<button <button
className="capitalize hover:text-vega-pink dark:hover:text-vega-yellow underline" className="hover:text-vega-pink dark:hover:text-vega-yellow underline"
data-testid={`web3-connector-${connectorName}`} data-testid={`web3-connector-${info.name}`}
onClick={async () => { onClick={async () => {
await connector.activate(desiredChainId); await connector.activate(desiredChainId);
setDialogOpen(false); setDialogOpen(false);
}} }}
> >
{connectorName} {info.text}
</button> </button>
</li> </li>
); );
@ -48,8 +48,16 @@ export const Web3ConnectDialog = ({
); );
}; };
function getConnectorName(connector: Connector) { function getConnectorInfo(connector: Connector) {
if (connector instanceof MetaMask) return 'MetaMask'; if (connector instanceof MetaMask)
if (connector instanceof WalletConnect) return 'WalletConnect'; return {
return 'Unknown'; 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) => { }: RecipientCellProps) => {
return ( return (
<Link <Link
title={t('View address on Etherscan')} title={t('View on Etherscan (opens in a new tab)')}
href={`${ethUrl}/address/${value}`} href={`${ethUrl}/address/${value}`}
data-testid="etherscan-link" data-testid="etherscan-link"
> >