Style updates, Approve UI updates (#841)

improvements(approvals): match approve flow to add/remove, update UI styles
This commit is contained in:
Ian Lapham 2020-06-05 13:20:47 -04:00 committed by GitHub
parent c133c472be
commit 0004db3d4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 327 additions and 248 deletions

@ -1,4 +1,4 @@
import { TEST_ADDRESS_NEVER_USE } from '../support/commands' import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands'
describe('Landing Page', () => { describe('Landing Page', () => {
beforeEach(() => cy.visit('/')) beforeEach(() => cy.visit('/'))
@ -22,6 +22,6 @@ describe('Landing Page', () => {
it('is connected', () => { it('is connected', () => {
cy.get('#web3-status-connected').click() cy.get('#web3-status-connected').click()
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE) cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
}) })
}) })

@ -1,5 +1,7 @@
export const TEST_ADDRESS_NEVER_USE: string export const TEST_ADDRESS_NEVER_USE: string
export const TEST_ADDRESS_NEVER_USE_SHORTENED: string
// declare namespace Cypress { // declare namespace Cypress {
// // eslint-disable-next-line @typescript-eslint/class-name-casing // // eslint-disable-next-line @typescript-eslint/class-name-casing
// interface cy { // interface cy {

@ -14,6 +14,8 @@ const PRIVATE_KEY_TEST_NEVER_USE = '0xad20c82497421e9784f18460ad2fe84f73569068e9
// address of the above key // address of the above key
export const TEST_ADDRESS_NEVER_USE = '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5' export const TEST_ADDRESS_NEVER_USE = '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5'
export const TEST_ADDRESS_NEVER_USE_SHORTENED = '0x0fF2...F4a5'
class CustomizedBridge extends _Eip1193Bridge { class CustomizedBridge extends _Eip1193Bridge {
async sendAsync(...args) { async sendAsync(...args) {
console.debug('sendAsync called', ...args) console.debug('sendAsync called', ...args)

@ -6,21 +6,21 @@ import { LinkStyledButton } from '../../theme'
import { CheckCircle, Copy } from 'react-feather' import { CheckCircle, Copy } from 'react-feather'
const CopyIcon = styled(LinkStyledButton)` const CopyIcon = styled(LinkStyledButton)`
color: ${({ theme }) => theme.text4}; color: ${({ theme }) => theme.text3};
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
margin-right: 1rem;
margin-left: 0.5rem;
text-decoration: none; text-decoration: none;
font-size: 0.825rem;
:hover, :hover,
:active, :active,
:focus { :focus {
text-decoration: none; text-decoration: none;
color: ${({ theme }) => theme.text3}; color: ${({ theme }) => theme.text2};
} }
` `
const TransactionStatusText = styled.span` const TransactionStatusText = styled.span`
margin-left: 0.25rem; margin-left: 0.25rem;
font-size: 0.825rem;
${({ theme }) => theme.flexRowNoWrap}; ${({ theme }) => theme.flexRowNoWrap};
align-items: center; align-items: center;
` `
@ -30,7 +30,6 @@ export default function CopyHelper(props: { toCopy: string; children?: React.Rea
return ( return (
<CopyIcon onClick={() => setCopied(props.toCopy)}> <CopyIcon onClick={() => setCopied(props.toCopy)}>
{props.children}
{isCopied ? ( {isCopied ? (
<TransactionStatusText> <TransactionStatusText>
<CheckCircle size={'16'} /> <CheckCircle size={'16'} />
@ -41,6 +40,7 @@ export default function CopyHelper(props: { toCopy: string; children?: React.Rea
<Copy size={'16'} /> <Copy size={'16'} />
</TransactionStatusText> </TransactionStatusText>
)} )}
{isCopied ? '' : props.children}
</CopyIcon> </CopyIcon>
) )
} }

@ -1,55 +1,39 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { Check, Triangle } from 'react-feather' import { CheckCircle, Triangle, ExternalLink as LinkIcon } from 'react-feather'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils' import { getEtherscanLink } from '../../utils'
import { ExternalLink, Spinner } from '../../theme' import { ExternalLink } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { transparentize } from 'polished'
import { useAllTransactions } from '../../state/transactions/hooks' import { useAllTransactions } from '../../state/transactions/hooks'
import { RowFixed } from '../Row'
import Loader from '../Loader'
const TransactionWrapper = styled.div` const TransactionWrapper = styled.div``
margin-top: 0.75rem;
`
const TransactionStatusText = styled.div` const TransactionStatusText = styled.div`
margin-right: 0.5rem; margin-right: 0.5rem;
display: flex;
align-items: center;
:hover {
text-decoration: underline;
}
` `
const TransactionState = styled(ExternalLink)<{ pending: boolean; success?: boolean }>` const TransactionState = styled(ExternalLink)<{ pending: boolean; success?: boolean }>`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
text-decoration: none !important; text-decoration: none !important;
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.5rem 0.75rem; padding: 0.25rem 0rem;
font-weight: 500; font-weight: 500;
font-size: 0.75rem; font-size: 0.825rem;
border: 1px solid; color: ${({ theme }) => theme.primary1};
color: ${({ pending, success, theme }) => (pending ? theme.primary1 : success ? theme.green1 : theme.red1)};
border-color: ${({ pending, success, theme }) =>
pending
? transparentize(0.75, theme.primary1)
: success
? transparentize(0.75, theme.green1)
: transparentize(0.75, theme.red1)};
:hover {
border-color: ${({ pending, success, theme }) =>
pending
? transparentize(0, theme.primary1)
: success
? transparentize(0, theme.green1)
: transparentize(0, theme.red1)};
}
` `
const IconWrapper = styled.div` const IconWrapper = styled.div<{ pending: boolean; success?: boolean }>`
flex-shrink: 0; color: ${({ pending, success, theme }) => (pending ? theme.primary1 : success ? theme.green1 : theme.red1)};
` `
export default function Transaction({ hash }: { hash: string }) { export default function Transaction({ hash }: { hash: string }) {
@ -65,9 +49,12 @@ export default function Transaction({ hash }: { hash: string }) {
return ( return (
<TransactionWrapper> <TransactionWrapper>
<TransactionState href={getEtherscanLink(chainId, hash, 'transaction')} pending={pending} success={success}> <TransactionState href={getEtherscanLink(chainId, hash, 'transaction')} pending={pending} success={success}>
<TransactionStatusText>{summary ? summary : hash}</TransactionStatusText> <RowFixed>
<IconWrapper> <TransactionStatusText>{summary ? summary : hash}</TransactionStatusText>
{pending ? <Spinner src={Circle} /> : success ? <Check size="16" /> : <Triangle size="16" />} <LinkIcon size={16} />
</RowFixed>
<IconWrapper pending={pending} success={success}>
{pending ? <Loader /> : success ? <CheckCircle size="16" /> : <Triangle size="16" />}
</IconWrapper> </IconWrapper>
</TransactionState> </TransactionState>
</TransactionWrapper> </TransactionWrapper>

@ -2,9 +2,9 @@ import React, { useCallback, useContext } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { isMobile } from 'react-device-detect'
import { AppDispatch } from '../../state' import { AppDispatch } from '../../state'
import { clearAllTransactions } from '../../state/transactions/actions' import { clearAllTransactions } from '../../state/transactions/actions'
import { shortenAddress } from '../../utils'
import { AutoRow } from '../Row' import { AutoRow } from '../Row'
import Copy from './Copy' import Copy from './Copy'
import Transaction from './Transaction' import Transaction from './Transaction'
@ -18,9 +18,8 @@ import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png' import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png' import PortisIcon from '../../assets/images/portisIcon.png'
import Identicon from '../Identicon' import Identicon from '../Identicon'
import { ButtonSecondary } from '../Button'
import { ButtonEmpty } from '../Button' import { ExternalLink as LinkIcon } from 'react-feather'
import { ExternalLink, LinkStyledButton, TYPE } from '../../theme' import { ExternalLink, LinkStyledButton, TYPE } from '../../theme'
const HeaderRow = styled.div` const HeaderRow = styled.div`
@ -55,31 +54,31 @@ const UpperSection = styled.div`
const InfoCard = styled.div` const InfoCard = styled.div`
padding: 1rem; padding: 1rem;
background-color: ${({ theme }) => theme.bg2}; border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 20px; border-radius: 20px;
position: relative;
display: grid;
grid-row-gap: 12px;
margin-bottom: 20px;
` `
const AccountGroupingRow = styled.div` const AccountGroupingRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}; ${({ theme }) => theme.flexRowNoWrap};
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
font-weight: 500; font-weight: 400;
color: ${({ theme }) => theme.text1}; color: ${({ theme }) => theme.text1};
div { div {
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: center; align-items: center;
} }
&:first-of-type {
margin-bottom: 8px;
}
` `
const AccountSection = styled.div` const AccountSection = styled.div`
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg1};
padding: 0rem 1rem; padding: 0rem 1rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1rem 1rem;`}; ${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
` `
const YourAccount = styled.div` const YourAccount = styled.div`
@ -94,28 +93,6 @@ const YourAccount = styled.div`
} }
` `
const GreenCircle = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
&:first-child {
height: 8px;
width: 8px;
margin-left: 12px;
margin-right: 2px;
background-color: ${({ theme }) => theme.green1};
border-radius: 50%;
}
`
const CircleWrapper = styled.div`
color: ${({ theme }) => theme.green1};
display: flex;
justify-content: center;
align-items: center;
`
const LowerSection = styled.div` const LowerSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap} ${({ theme }) => theme.flexColumnNoWrap}
padding: 1.5rem; padding: 1.5rem;
@ -132,13 +109,14 @@ const LowerSection = styled.div`
} }
` `
const AccountControl = styled.div<{ hasENS: boolean; isENS: boolean }>` const AccountControl = styled.div`
${({ theme }) => theme.flexRowNoWrap}; display: flex;
align-items: center; justify-content: space-between;
min-width: 0; min-width: 0;
width: 100%;
font-weight: ${({ hasENS, isENS }) => (hasENS ? (isENS ? 500 : 400) : 500)}; font-weight: 500;
font-size: ${({ hasENS, isENS }) => (hasENS ? (isENS ? '1rem' : '0.8rem') : '1rem')}; font-size: 1.25rem;
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
@ -146,22 +124,22 @@ const AccountControl = styled.div<{ hasENS: boolean; isENS: boolean }>`
p { p {
min-width: 0; min-width: 0;
margin: 0.5rem 0; margin: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
` `
const ConnectButtonRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: center;
margin: 10px 0;
`
const AddressLink = styled(ExternalLink)<{ hasENS: boolean; isENS: boolean }>` const AddressLink = styled(ExternalLink)<{ hasENS: boolean; isENS: boolean }>`
color: ${({ hasENS, isENS, theme }) => (hasENS ? (isENS ? theme.primary1 : theme.text3) : theme.primary1)}; font-size: 0.825rem;
color: ${({ theme }) => theme.text3};
margin-left: 1rem;
font-size: 0.825rem;
display: flex;
:hover {
color: ${({ theme }) => theme.text2};
}
` `
const CloseIcon = styled.div` const CloseIcon = styled.div`
@ -181,14 +159,17 @@ const CloseColor = styled(Close)`
` `
const WalletName = styled.div` const WalletName = styled.div`
padding-left: 0.5rem;
width: initial; width: initial;
font-size: 0.825rem;
font-weight: 500;
color: ${({ theme }) => theme.text3};
` `
const IconWrapper = styled.div<{ size?: number }>` const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap}; ${({ theme }) => theme.flexColumnNoWrap};
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-right: 8px;
& > img, & > img,
span { span {
height: ${({ size }) => (size ? size + 'px' : '32px')}; height: ${({ size }) => (size ? size + 'px' : '32px')};
@ -203,10 +184,12 @@ const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap}; ${({ theme }) => theme.flexColumnNoWrap};
` `
const WalletAction = styled.div` const WalletAction = styled(ButtonSecondary)`
color: ${({ theme }) => theme.text4}; width: fit-content;
margin-left: 16px;
font-weight: 400; font-weight: 400;
margin-left: 8px;
font-size: 0.825rem;
padding: 4px 6px;
:hover { :hover {
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
@ -255,39 +238,39 @@ export default function AccountDetails({
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK')) SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
) )
.map(k => SUPPORTED_WALLETS[k].name)[0] .map(k => SUPPORTED_WALLETS[k].name)[0]
return <WalletName>{name}</WalletName> return <WalletName>Connected with {name}</WalletName>
} }
function getStatusIcon() { function getStatusIcon() {
if (connector === injected) { if (connector === injected) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<Identicon /> {formatConnectorName()} <Identicon />
</IconWrapper> </IconWrapper>
) )
} else if (connector === walletconnect) { } else if (connector === walletconnect) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} /> {formatConnectorName()} <img src={WalletConnectIcon} alt={''} />
</IconWrapper> </IconWrapper>
) )
} else if (connector === walletlink) { } else if (connector === walletlink) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} /> {formatConnectorName()} <img src={CoinbaseWalletIcon} alt={''} />
</IconWrapper> </IconWrapper>
) )
} else if (connector === fortmatic) { } else if (connector === fortmatic) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} /> {formatConnectorName()} <img src={FortmaticIcon} alt={''} />
</IconWrapper> </IconWrapper>
) )
} else if (connector === portis) { } else if (connector === portis) {
return ( return (
<> <>
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={PortisIcon} alt={''} /> {formatConnectorName()} <img src={PortisIcon} alt={''} />
<MainWalletAction <MainWalletAction
onClick={() => { onClick={() => {
portis.portis.showPortis() portis.portis.showPortis()
@ -320,10 +303,11 @@ export default function AccountDetails({
<YourAccount> <YourAccount>
<InfoCard> <InfoCard>
<AccountGroupingRow> <AccountGroupingRow>
{getStatusIcon()} {formatConnectorName()}
<div> <div>
{connector !== injected && connector !== walletlink && ( {connector !== injected && connector !== walletlink && (
<WalletAction <WalletAction
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
onClick={() => { onClick={() => {
;(connector as any).close() ;(connector as any).close()
}} }}
@ -331,73 +315,82 @@ export default function AccountDetails({
Disconnect Disconnect
</WalletAction> </WalletAction>
)} )}
<CircleWrapper> <WalletAction
<GreenCircle> style={{ fontSize: '.825rem', fontWeight: 400 }}
<div /> onClick={() => {
</GreenCircle> openOptions()
</CircleWrapper> }}
>
Change
</WalletAction>
</div> </div>
</AccountGroupingRow> </AccountGroupingRow>
<AccountGroupingRow id="web3-account-identifier-row"> <AccountGroupingRow id="web3-account-identifier-row">
{ENSName ? ( <AccountControl>
<> {ENSName ? (
<AccountControl hasENS={!!ENSName} isENS={true}> <>
<p>{ENSName}</p> <Copy toCopy={account} /> <div>
</AccountControl> {getStatusIcon()}
</> <p> {ENSName}</p>
) : ( </div>
<> </>
<AccountControl hasENS={!!ENSName} isENS={false}> ) : (
<p>{account}</p> <Copy toCopy={account} /> <>
</AccountControl> <div>
</> {getStatusIcon()}
)} <p> {shortenAddress(account)}</p>
</div>
</>
)}
</AccountControl>
</AccountGroupingRow> </AccountGroupingRow>
<AccountGroupingRow> <AccountGroupingRow>
{ENSName ? ( {ENSName ? (
<> <>
<AccountControl hasENS={!!ENSName} isENS={false}> <AccountControl>
<AddressLink hasENS={!!ENSName} isENS={true} href={getEtherscanLink(chainId, ENSName, 'address')}> <div>
View on Etherscan <Copy toCopy={account}>
</AddressLink> <span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
<AddressLink
hasENS={!!ENSName}
isENS={true}
href={getEtherscanLink(chainId, ENSName, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
</div>
</AccountControl> </AccountControl>
</> </>
) : ( ) : (
<> <>
<AccountControl hasENS={!!ENSName} isENS={false}> <AccountControl>
<AddressLink <div>
hasENS={!!ENSName} <Copy toCopy={account}>
isENS={false} <span style={{ marginLeft: '4px' }}>Copy Address</span>
href={getEtherscanLink(chainId, account, 'address')} </Copy>
> <AddressLink
View on Etherscan hasENS={!!ENSName}
</AddressLink> isENS={false}
href={getEtherscanLink(chainId, account, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
</div>
</AccountControl> </AccountControl>
</> </>
)} )}
{/* {formatConnectorName()} */}
</AccountGroupingRow> </AccountGroupingRow>
</InfoCard> </InfoCard>
</YourAccount> </YourAccount>
{!(isMobile && (window.web3 || window.ethereum)) && (
<ConnectButtonRow>
<ButtonEmpty
style={{ fontWeight: 400 }}
padding={'12px'}
width={'260px'}
onClick={() => {
openOptions()
}}
>
Connect to a different wallet
</ButtonEmpty>
</ConnectButtonRow>
)}
</AccountSection> </AccountSection>
</UpperSection> </UpperSection>
{!!pendingTransactions.length || !!confirmedTransactions.length ? ( {!!pendingTransactions.length || !!confirmedTransactions.length ? (
<LowerSection> <LowerSection>
<AutoRow style={{ justifyContent: 'space-between' }}> <AutoRow mb={'1rem'} style={{ justifyContent: 'space-between' }}>
<TYPE.body>Recent Transactions</TYPE.body> <TYPE.body>Recent Transactions</TYPE.body>
<LinkStyledButton onClick={clearAllTransactionsCallback}>(clear all)</LinkStyledButton> <LinkStyledButton onClick={clearAllTransactionsCallback}>(clear all)</LinkStyledButton>
</AutoRow> </AutoRow>

@ -6,7 +6,12 @@ import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
import { Button as RebassButton, ButtonProps } from 'rebass/styled-components' import { Button as RebassButton, ButtonProps } from 'rebass/styled-components'
const Base = styled(RebassButton)<{ padding?: string; width?: string; borderRadius?: string }>` const Base = styled(RebassButton)<{
padding?: string
width?: string
borderRadius?: string
altDisbaledStyle?: boolean
}>`
padding: ${({ padding }) => (padding ? padding : '18px')}; padding: ${({ padding }) => (padding ? padding : '18px')};
width: ${({ width }) => (width ? width : '100%')}; width: ${({ width }) => (width ? width : '100%')};
font-weight: 500; font-weight: 500;
@ -45,10 +50,12 @@ export const ButtonPrimary = styled(Base)`
background-color: ${({ theme }) => darken(0.1, theme.primary1)}; background-color: ${({ theme }) => darken(0.1, theme.primary1)};
} }
&:disabled { &:disabled {
background-color: ${({ theme }) => theme.bg3}; background-color: ${({ theme, altDisbaledStyle }) => (altDisbaledStyle ? theme.primary1 : theme.bg3)};
color: ${({ theme }) => theme.text3} color: ${({ theme, altDisbaledStyle }) => (altDisbaledStyle ? 'white' : theme.text3)}
cursor: auto; cursor: auto;
box-shadow: none; box-shadow: none;
border: 1px solid transparent;;
outline: none;
} }
` `
@ -68,6 +75,16 @@ export const ButtonLight = styled(Base)`
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.05, theme.primary5)}; box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.05, theme.primary5)};
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.primary5)}; background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.primary5)};
} }
:disabled {
opacity: 0.4;
:hover {
cursor: auto;
background-color: ${({ theme }) => theme.primary5};
box-shadow: none;
border: 1px solid transparent;
outline: none;
}
}
` `
export const ButtonGray = styled(Base)` export const ButtonGray = styled(Base)`

@ -1,14 +1,14 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import Modal from '../Modal' import Modal from '../Modal'
import Loader from '../Loader'
import { ExternalLink } from '../../theme' import { ExternalLink } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import { CloseIcon } from '../../theme/components' import { CloseIcon, Spinner } from '../../theme/components'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { ArrowUpCircle } from 'react-feather' import { ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button' import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column' import { AutoColumn, ColumnCenter } from '../Column'
import Circle from '../../assets/images/blue-loader.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils' import { getEtherscanLink } from '../../utils'
@ -30,6 +30,11 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0; padding: 60px 0;
` `
const CustomLightSpinner = styled(Spinner)<{ size: string }>`
height: ${({ size }) => size};
width: ${({ size }) => size};
`
interface ConfirmationModalProps { interface ConfirmationModalProps {
isOpen: boolean isOpen: boolean
onDismiss: () => void onDismiss: () => void
@ -80,7 +85,7 @@ export default function ConfirmationModal({
</RowBetween> </RowBetween>
<ConfirmedIcon> <ConfirmedIcon>
{pendingConfirmation ? ( {pendingConfirmation ? (
<Loader size="90px" /> <CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
) : ( ) : (
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} /> <ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
)} )}

@ -49,7 +49,6 @@ const LabelRow = styled.div`
font-size: 0.75rem; font-size: 0.75rem;
line-height: 1rem; line-height: 1rem;
padding: 0.75rem 1rem 0 1rem; padding: 0.75rem 1rem 0 1rem;
height: 20px;
span:hover { span:hover {
cursor: pointer; cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.text2)}; color: ${({ theme }) => darken(0.2, theme.text2)};

@ -138,7 +138,8 @@ const VersionLabel = styled.span<{ isV2?: boolean }>`
const VersionToggle = styled.a` const VersionToggle = styled.a`
border-radius: 16px; border-radius: 16px;
border: 1px solid ${({ theme }) => theme.primary1}; background: ${({ theme }) => theme.primary5};
border: 1px solid ${({ theme }) => theme.primary4};
color: ${({ theme }) => theme.primary1}; color: ${({ theme }) => theme.primary1};
display: flex; display: flex;
width: fit-content; width: fit-content;
@ -204,7 +205,7 @@ export default function Header() {
</TestnetWrapper> </TestnetWrapper>
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}> <AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? ( {account && userEthBalance ? (
<Text style={{ flexShrink: 0 }} px="0.5rem" fontWeight={500}> <Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
{userEthBalance?.toSignificant(4)} ETH {userEthBalance?.toSignificant(4)} ETH
</Text> </Text>
) : null} ) : null}

@ -1,15 +1,38 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled, { keyframes } from 'styled-components'
import { Spinner } from '../../theme' const rotate = keyframes`
import Circle from '../../assets/images/blue-loader.svg' from {
transform: rotate(0deg);
const SpinnerWrapper = styled(Spinner)<{ size: string }>` }
height: ${({ size }) => size}; to {
width: ${({ size }) => size}; transform: rotate(360deg);
}
` `
export default function Loader({ size }: { size: string }) { const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
return <SpinnerWrapper src={Circle} alt="loader" size={size} /> animation: 2s ${rotate} linear infinite;
height: ${({ size }) => size};
width: ${({ size }) => size};
path {
stroke: ${({ stroke, theme }) => stroke ?? theme.primary1};
}
`
/**
* Takes in custom size and stroke for circle color, default to primary color as fill,
* need ...rest for layered styles on top
*/
export default function Loader({ size = '16px', stroke = null, ...rest }: { size?: string; stroke?: string }) {
return (
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
<path
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375 19.1414 5"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</StyledSVG>
)
} }

@ -42,8 +42,10 @@ const StyledDialogOverlay = styled(({ mobile, ...rest }) => <AnimatedDialogOverl
// destructure to not pass custom props to Dialog DOM element // destructure to not pass custom props to Dialog DOM element
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => ( const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
<DialogContent aria-label="content" {...rest} /> <DialogContent {...rest} />
))` )).attrs({
'aria-label': 'dialog'
})`
&[data-reach-dialog-content] { &[data-reach-dialog-content] {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.bg1}; border: 1px solid ${({ theme }) => theme.bg1};
@ -170,7 +172,7 @@ export default function Modal({
hidden={true} hidden={true}
minHeight={minHeight} minHeight={minHeight}
maxHeight={maxHeight} maxHeight={maxHeight}
mobile={isMobile} mobile={isMobile ?? undefined}
> >
<HiddenCloseButton onClick={onDismiss} /> <HiddenCloseButton onClick={onDismiss} />
{children} {children}
@ -189,13 +191,7 @@ export default function Modal({
{transitions.map( {transitions.map(
({ item, key, props }) => ({ item, key, props }) =>
item && ( item && (
<StyledDialogOverlay <StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
mobile={false}
>
<StyledDialogContent hidden={true} minHeight={minHeight} maxHeight={maxHeight} isOpen={isOpen}> <StyledDialogContent hidden={true} minHeight={minHeight} maxHeight={maxHeight} isOpen={isOpen}>
<HiddenCloseButton onClick={onDismiss} /> <HiddenCloseButton onClick={onDismiss} />
{children} {children}

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import Circle from '../../assets/images/circle.svg'
import { ALL_TOKENS } from '../../constants/tokens' import { ALL_TOKENS } from '../../constants/tokens'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { LinkStyledButton, TYPE } from '../../theme' import { LinkStyledButton, TYPE } from '../../theme'
@ -13,7 +12,8 @@ import { ButtonSecondary } from '../Button'
import Column, { AutoColumn } from '../Column' import Column, { AutoColumn } from '../Column'
import { RowFixed } from '../Row' import { RowFixed } from '../Row'
import TokenLogo from '../TokenLogo' import TokenLogo from '../TokenLogo'
import { FadedSpan, GreySpan, MenuItem, SpinnerWrapper, ModalInfo } from './styleds' import { FadedSpan, GreySpan, MenuItem, ModalInfo } from './styleds'
import Loader from '../Loader'
function isDefaultToken(tokenAddress: string, chainId?: number): boolean { function isDefaultToken(tokenAddress: string, chainId?: number): boolean {
const address = isAddress(tokenAddress) const address = isAddress(tokenAddress)
@ -112,7 +112,7 @@ export default function TokenList({
)} )}
</Text> </Text>
) : account ? ( ) : account ? (
<SpinnerWrapper src={Circle} alt="loader" /> <Loader />
) : ( ) : (
'-' '-'
)} )}

@ -1,5 +1,4 @@
import styled from 'styled-components' import styled from 'styled-components'
import { Spinner } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { AutoRow, RowBetween, RowFixed } from '../Row' import { AutoRow, RowBetween, RowFixed } from '../Row'
@ -23,12 +22,6 @@ export const GreySpan = styled.span`
font-weight: 400; font-weight: 400;
` `
export const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
color: ${({ theme }) => theme.text4};
opacity: 0.6;
`
export const Input = styled.input` export const Input = styled.input`
position: relative; position: relative;
display: flex; display: flex;

@ -224,7 +224,7 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
<AutoColumn gap="sm"> <AutoColumn gap="sm">
<RowFixed padding={'0 20px'}> <RowFixed padding={'0 20px'}>
<TYPE.black fontSize={14} color={theme.text2}> <TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
Deadline Deadline
</TYPE.black> </TYPE.black>
<QuestionHelper text="Your transaction will revert if it is pending for more than this long." /> <QuestionHelper text="Your transaction will revert if it is pending for more than this long." />

@ -18,11 +18,11 @@ const Wrapper = styled.div<{ error: boolean }>`
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)}; background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
position: relative; position: relative;
padding: 1rem; padding: 1rem;
border: 0.5px solid ${({ theme, error }) => transparentize(0.4, error ? theme.red1 : theme.yellow1)}; /* border: 0.5px solid ${({ theme, error }) => transparentize(0.4, error ? theme.red1 : theme.yellow1)}; */
border-radius: 10px; border-radius: 10px;
margin-bottom: 20px; margin-bottom: 20px;
display: grid; display: grid;
grid-template-rows: auto auto auto; grid-template-rows: 14px auto auto;
grid-row-gap: 14px; grid-row-gap: 14px;
` `
@ -42,15 +42,15 @@ const CloseColor = styled(Close)`
const CloseIcon = styled.div` const CloseIcon = styled.div`
position: absolute; position: absolute;
right: 1rem; right: 1rem;
top: 14px; top: 12px;
&:hover { &:hover {
cursor: pointer; cursor: pointer;
opacity: 0.6; opacity: 0.6;
} }
& > * { & > * {
height: 14px; height: 16px;
width: 14px; width: 16px;
} }
` `

@ -5,9 +5,8 @@ import Option from './Option'
import { SUPPORTED_WALLETS } from '../../constants' import { SUPPORTED_WALLETS } from '../../constants'
import WalletConnectData from './WalletConnectData' import WalletConnectData from './WalletConnectData'
import { walletconnect, injected } from '../../connectors' import { walletconnect, injected } from '../../connectors'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { darken } from 'polished' import { darken } from 'polished'
import Loader from '../Loader'
const PendingSection = styled.div` const PendingSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap}; ${({ theme }) => theme.flexColumnNoWrap};
@ -19,14 +18,8 @@ const PendingSection = styled.div`
} }
` `
const SpinnerWrapper = styled(Spinner)` const StyledLoader = styled(Loader)`
font-size: 4rem;
margin-right: 1rem; margin-right: 1rem;
svg {
path {
color: ${({ theme }) => theme.bg4};
}
}
` `
const LoadingMessage = styled.div<{ error?: boolean }>` const LoadingMessage = styled.div<{ error?: boolean }>`
@ -93,7 +86,7 @@ export default function PendingView({
{!error && connector === walletconnect && <WalletConnectData size={size} uri={uri} />} {!error && connector === walletconnect && <WalletConnectData size={size} uri={uri} />}
<LoadingMessage error={error}> <LoadingMessage error={error}>
<LoadingWrapper> <LoadingWrapper>
{!error && <SpinnerWrapper src={Circle} />} {!error && <StyledLoader />}
{error ? ( {error ? (
<ErrorGroup> <ErrorGroup>
<div>Error connecting.</div> <div>Error connecting.</div>

@ -5,9 +5,8 @@ import { useTranslation } from 'react-i18next'
import { network } from '../../connectors' import { network } from '../../connectors'
import { useEagerConnect, useInactiveListener } from '../../hooks' import { useEagerConnect, useInactiveListener } from '../../hooks'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { NetworkContextName } from '../../constants' import { NetworkContextName } from '../../constants'
import Loader from '../Loader'
const MessageWrapper = styled.div` const MessageWrapper = styled.div`
display: flex; display: flex;
@ -20,16 +19,6 @@ const Message = styled.h2`
color: ${({ theme }) => theme.secondary1}; color: ${({ theme }) => theme.secondary1};
` `
const SpinnerWrapper = styled(Spinner)`
font-size: 4rem;
svg {
path {
color: ${({ theme }) => theme.secondary1};
}
}
`
export default function Web3ReactManager({ children }) { export default function Web3ReactManager({ children }) {
const { t } = useTranslation() const { t } = useTranslation()
const { active } = useWeb3React() const { active } = useWeb3React()
@ -78,7 +67,7 @@ export default function Web3ReactManager({ children }) {
if (!active && !networkActive) { if (!active && !networkActive) {
return showLoader ? ( return showLoader ? (
<MessageWrapper> <MessageWrapper>
<SpinnerWrapper src={Circle} /> <Loader />
</MessageWrapper> </MessageWrapper>
) : null ) : null
} }

@ -16,18 +16,12 @@ import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg' import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg' import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import { Spinner } from '../../theme'
import LightCircle from '../../assets/svg/lightcircle.svg'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { shortenAddress } from '../../utils' import { shortenAddress } from '../../utils'
import { useAllTransactions } from '../../state/transactions/hooks' import { useAllTransactions } from '../../state/transactions/hooks'
import { NetworkContextName } from '../../constants' import { NetworkContextName } from '../../constants'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors' import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import Loader from '../Loader'
const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
`
const IconWrapper = styled.div<{ size?: number }>` const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap}; ${({ theme }) => theme.flexColumnNoWrap};
@ -189,7 +183,7 @@ export default function Web3Status() {
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}> <Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions ? ( {hasPendingTransactions ? (
<RowBetween> <RowBetween>
<Text>{pending?.length} Pending</Text> <SpinnerWrapper src={LightCircle} alt="loader" /> <Text>{pending?.length} Pending</Text> <Loader stroke="white" />
</RowBetween> </RowBetween>
) : ( ) : (
<Text>{ENSName || shortenAddress(account)}</Text> <Text>{ENSName || shortenAddress(account)}</Text>

@ -95,6 +95,16 @@ export default function Send({ location: { search } }: RouteComponentProps) {
// check whether the user has approved the router on the input token // check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(bestTrade, allowedSlippage) const [approval, approveCallback] = useApproveCallbackFromTrade(bestTrade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const formattedAmounts = { const formattedAmounts = {
[independentField]: typedValue, [independentField]: typedValue,
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(6) : '' [dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(6) : ''
@ -181,6 +191,13 @@ export default function Send({ location: { search } }: RouteComponentProps) {
// warnings on slippage // warnings on slippage
const severity = !sendingWithSwap ? 0 : warningSeverity(priceImpactWithoutFee) const severity = !sendingWithSwap ? 0 : warningSeverity(priceImpactWithoutFee)
// show approval buttons when: no errors on input, not approved or pending, or has been approved in this session
const showApproveFlow =
((sendingWithSwap && isSwapValid) || (!sendingWithSwap && isSendValid)) &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED))
function modalHeader() { function modalHeader() {
if (!sendingWithSwap) { if (!sendingWithSwap) {
return <TransferModalHeader amount={parsedAmounts?.[Field.INPUT]} ENSName={ENS} recipient={recipient} /> return <TransferModalHeader amount={parsedAmounts?.[Field.INPUT]} ENSName={ENS} recipient={recipient} />
@ -363,11 +380,13 @@ export default function Send({ location: { search } }: RouteComponentProps) {
onMax={() => { onMax={() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact()) maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}} }}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)} onTokenSelection={address => {
setApprovalSubmitted(false)
onTokenSelection(Field.INPUT, address)
}}
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address} otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
id="swap-currency-input" id="swap-currency-input"
/> />
{sendingWithSwap ? ( {sendingWithSwap ? (
<ColumnCenter> <ColumnCenter>
<RowBetween padding="0 1rem 0 12px"> <RowBetween padding="0 1rem 0 12px">
@ -431,7 +450,7 @@ export default function Send({ location: { search } }: RouteComponentProps) {
/> />
</AutoColumn> </AutoColumn>
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && ( {!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<Card padding={'.25rem 1.25rem 0 .75rem'} borderRadius={'20px'}> <Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
<AutoColumn gap="4px"> <AutoColumn gap="4px">
<RowBetween align="center"> <RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}> <Text fontWeight={500} fontSize={14} color={theme.text2}>
@ -471,14 +490,36 @@ export default function Send({ location: { search } }: RouteComponentProps) {
<GreyCard style={{ textAlign: 'center' }}> <GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main> <TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
</GreyCard> </GreyCard>
) : approval === ApprovalState.NOT_APPROVED || approval === ApprovalState.PENDING ? ( ) : showApproveFlow ? (
<ButtonLight onClick={approveCallback} disabled={approval === ApprovalState.PENDING}> <RowBetween>
{approval === ApprovalState.PENDING ? ( <ButtonPrimary
<Dots>Approving {tokens[Field.INPUT]?.symbol}</Dots> onClick={approveCallback}
) : ( disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
'Approve ' + tokens[Field.INPUT]?.symbol width="48%"
)} altDisbaledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
</ButtonLight> >
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + tokens[Field.INPUT]?.symbol
)}
</ButtonPrimary>
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
width="48%"
id="send-button"
disabled={approval !== ApprovalState.APPROVED}
error={sendingWithSwap && isSwapValid && severity > 2}
>
<Text fontSize={16} fontWeight={500}>
{`Send${severity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : ( ) : (
<ButtonError <ButtonError
onClick={() => { onClick={() => {

@ -1,11 +1,11 @@
import { JSBI, TokenAmount, WETH } from '@uniswap/sdk' import { JSBI, TokenAmount, WETH } from '@uniswap/sdk'
import React, { useContext, useState } from 'react' import React, { useContext, useState, useEffect } from 'react'
import { ArrowDown } from 'react-feather' import { ArrowDown } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { ButtonError, ButtonLight } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import Card, { GreyCard } from '../../components/Card' import Card, { GreyCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmationModal from '../../components/ConfirmationModal'
@ -81,6 +81,23 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
// check whether the user has approved the router on the input token // check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(bestTrade, allowedSlippage) const [approval, approveCallback] = useApproveCallbackFromTrade(bestTrade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// show approve flow when: no error on inputs, not approved or pending, or approved in current session
const showApproveFlow =
!error &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED))
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: TokenAmount = const maxAmountInput: TokenAmount =
!!tokenBalances[Field.INPUT] && !!tokenBalances[Field.INPUT] &&
!!tokens[Field.INPUT] && !!tokens[Field.INPUT] &&
@ -203,7 +220,10 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
onMax={() => { onMax={() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact()) maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}} }}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)} onTokenSelection={address => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onTokenSelection(Field.INPUT, address)
}}
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address} otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
id="swap-currency-input" id="swap-currency-input"
/> />
@ -213,13 +233,15 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
<ArrowWrapper> <ArrowWrapper>
<ArrowDown <ArrowDown
size="16" size="16"
onClick={onSwitchTokens} onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
color={tokens[Field.INPUT] && tokens[Field.OUTPUT] ? theme.primary1 : theme.text2} color={tokens[Field.INPUT] && tokens[Field.OUTPUT] ? theme.primary1 : theme.text2}
/> />
</ArrowWrapper> </ArrowWrapper>
</AutoColumn> </AutoColumn>
</CursorPointer> </CursorPointer>
<CurrencyInputPanel <CurrencyInputPanel
field={Field.OUTPUT} field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]} value={formattedAmounts[Field.OUTPUT]}
@ -235,7 +257,7 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
</> </>
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && ( {!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<Card padding={'.25rem 1.25rem 0 .75rem'} borderRadius={'20px'}> <Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
<AutoColumn gap="4px"> <AutoColumn gap="4px">
<RowBetween align="center"> <RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}> <Text fontWeight={500} fontSize={14} color={theme.text2}>
@ -269,14 +291,36 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
<GreyCard style={{ textAlign: 'center' }}> <GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main> <TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
</GreyCard> </GreyCard>
) : approval === ApprovalState.NOT_APPROVED || approval === ApprovalState.PENDING ? ( ) : showApproveFlow ? (
<ButtonLight onClick={approveCallback} disabled={approval === ApprovalState.PENDING}> <RowBetween>
{approval === ApprovalState.PENDING ? ( <ButtonPrimary
<Dots>Approving {tokens[Field.INPUT]?.symbol}</Dots> onClick={approveCallback}
) : ( disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
'Approve ' + tokens[Field.INPUT]?.symbol width="48%"
)} altDisbaledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
</ButtonLight> >
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + tokens[Field.INPUT]?.symbol
)}
</ButtonPrimary>
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
width="48%"
id="swap-button"
disabled={!isValid || approval !== ApprovalState.APPROVED}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={500}>
{`Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : ( ) : (
<ButtonError <ButtonError
onClick={() => { onClick={() => {

@ -168,9 +168,9 @@ export const TYPE = {
export const FixedGlobalStyle = createGlobalStyle` export const FixedGlobalStyle = createGlobalStyle`
@import url('https://rsms.me/inter/inter.css'); @import url('https://rsms.me/inter/inter.css');
html { font-family: 'Inter', sans-serif; letter-spacing: -0.018em;} html, body, input, textarea, button { font-family: 'Inter', sans-serif; letter-spacing: -0.018em;}
@supports (font-variation-settings: normal) { @supports (font-variation-settings: normal) {
html { font-family: 'Inter var', sans-serif; } html, body, input, textarea, button { font-family: 'Inter var', sans-serif; }
} }
html, html,