feat(L2-beta-launch): network selector (#2129)
* fix an issue with optimism/arbitrum alert distinction, add a prompt to switch to Optimism on mainnet * only prompt to switch networks if the user has a wallet connected * add readmore link styles * add network to the user's wallet if it hasn't been added already * network selector * hide arbitrum until it launches * add arbitrum for testing * update copy and some margins * fix alert opacity issue * remove the launch alert :( * adjust icon position and add curvier corners * lighten some colors * keep the selector around even if the user's wallet doesn't support the eip when they're on L2, just hide all other networks * copy updates and some other small tweaks * better mobile experience * shrink on mobile * fix some links and css * differentiate between selector and row logos * fix some copy * remove network alert from add liquidity pages, update copy and buttons on swap page, remove close option if no eth, persist close state otherwise * design polish * update read more links * update downtime warning to be less intense * oe logo ' * design polish sesh * fix a couple bugs
This commit is contained in:
parent
119c79d623
commit
97ba8fb5b0
16
src/assets/svg/optimistic_ethereum.svg
Normal file
16
src/assets/svg/optimistic_ethereum.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<svg width="170" height="168" viewBox="0 0 170 168" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path opacity="0.6" d="M85.05 168C132.022 168 170.1 130.105 170.1 83.3593C170.1 36.6135 0 36.6135 0 83.3593C0 130.105 38.0782 168 85.05 168Z" fill="#FF505F"/>
|
||||
<path opacity="0.6" d="M85.05 168C132.022 168 170.1 130.105 170.1 83.3593C170.1 36.6135 0 36.6135 0 83.3593C0 130.105 38.0782 168 85.05 168Z" fill="#FF0320"/>
|
||||
<path d="M85.05 0C132.022 0 170.1 37.8949 170.1 84.6407C170.1 131.386 0 131.386 0 84.6407C0 37.8949 38.0782 0 85.05 0Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.665 64.0394L112.444 12.3742L89.0263 78.9477L144.665 64.0394Z" fill="#FF4E65"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M143.777 64.215L112.444 12.3742L165.349 58.4347L143.777 64.215Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.551 63.613L142.479 124.467L88.912 78.5213L144.551 63.613Z" fill="#D0001A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M143.663 63.7886L142.479 124.467L165.235 58.0083L143.663 63.7886Z" fill="#FF697B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="170" height="168" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -8,7 +8,7 @@ import useTheme from 'hooks/useTheme'
|
||||
|
||||
type ButtonProps = Omit<ButtonPropsOriginal, 'css'>
|
||||
|
||||
const Base = styled(RebassButton)<
|
||||
export const BaseButton = styled(RebassButton)<
|
||||
{
|
||||
padding?: string
|
||||
width?: string
|
||||
@ -50,7 +50,7 @@ const Base = styled(RebassButton)<
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPrimary = styled(Base)`
|
||||
export const ButtonPrimary = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
color: white;
|
||||
&:focus {
|
||||
@ -67,7 +67,8 @@ export const ButtonPrimary = styled(Base)`
|
||||
&:disabled {
|
||||
background-color: ${({ theme, altDisabledStyle, disabled }) =>
|
||||
altDisabledStyle ? (disabled ? theme.primary1 : theme.bg2) : theme.bg2};
|
||||
color: ${({ theme }) => theme.text2};
|
||||
color: ${({ altDisabledStyle, disabled, theme }) =>
|
||||
altDisabledStyle ? (disabled ? theme.white : theme.text2) : theme.text2};
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
@ -75,7 +76,7 @@ export const ButtonPrimary = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonLight = styled(Base)`
|
||||
export const ButtonLight = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
color: ${({ theme }) => theme.primaryText1};
|
||||
font-size: 16px;
|
||||
@ -103,7 +104,7 @@ export const ButtonLight = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonGray = styled(Base)`
|
||||
export const ButtonGray = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 16px;
|
||||
@ -117,7 +118,7 @@ export const ButtonGray = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonSecondary = styled(Base)`
|
||||
export const ButtonSecondary = styled(BaseButton)`
|
||||
border: 1px solid ${({ theme }) => theme.primary4};
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
background-color: transparent;
|
||||
@ -145,7 +146,7 @@ export const ButtonSecondary = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonOutlined = styled(Base)`
|
||||
export const ButtonOutlined = styled(BaseButton)`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
@ -164,7 +165,7 @@ export const ButtonOutlined = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(Base)`
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
color: white;
|
||||
&:focus {
|
||||
@ -185,7 +186,7 @@ export const ButtonYellow = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonEmpty = styled(Base)`
|
||||
export const ButtonEmpty = styled(BaseButton)`
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
display: flex;
|
||||
@ -207,7 +208,7 @@ export const ButtonEmpty = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonText = styled(Base)`
|
||||
export const ButtonText = styled(BaseButton)`
|
||||
padding: 0;
|
||||
width: fit-content;
|
||||
background: none;
|
||||
@ -229,7 +230,7 @@ export const ButtonText = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
const ButtonConfirmedStyle = styled(Base)`
|
||||
const ButtonConfirmedStyle = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
/* border: 1px solid ${({ theme }) => theme.green1}; */
|
||||
@ -242,7 +243,7 @@ const ButtonConfirmedStyle = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
const ButtonErrorStyle = styled(Base)`
|
||||
const ButtonErrorStyle = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.red1};
|
||||
border: 1px solid ${({ theme }) => theme.red1};
|
||||
|
||||
|
77
src/components/DowntimeWarning/index.tsx
Normal file
77
src/components/DowntimeWarning/index.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { L2_CHAIN_IDS, SupportedChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
|
||||
const Root = styled.div`
|
||||
background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')};
|
||||
border-radius: 18px;
|
||||
color: black;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
margin: 12px auto;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
max-width: 880px;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
display: block;
|
||||
margin: auto 16px auto 0;
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
`
|
||||
const ReadMoreLink = styled(ExternalLink)`
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
`
|
||||
|
||||
export default function DowntimeWarning() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const Content = () => {
|
||||
switch (chainId) {
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return (
|
||||
<div>
|
||||
<Trans>
|
||||
Optimistic Ethereum is in Beta and may experience downtime. Optimism expects planned downtime to upgrade
|
||||
the network in the near future. During downtime, your position will not earn fees and you will be unable
|
||||
to remove liquidity.{' '}
|
||||
<ReadMoreLink href="https://help.uniswap.org/en/articles/5406082-what-happens-if-the-optimistic-ethereum-network-experiences-downtime">
|
||||
Read more.
|
||||
</ReadMoreLink>
|
||||
</Trans>
|
||||
</div>
|
||||
)
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
case SupportedChainId.ARBITRUM_RINKEBY:
|
||||
return (
|
||||
<div>
|
||||
<Trans>
|
||||
Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you
|
||||
will be unable to remove liquidity.{' '}
|
||||
<ReadMoreLink href="https://help.uniswap.org/en/articles/5576122-arbitrum-network-downtime">
|
||||
Read more.
|
||||
</ReadMoreLink>
|
||||
</Trans>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<WarningIcon />
|
||||
<Content />
|
||||
</Root>
|
||||
)
|
||||
}
|
@ -60,7 +60,7 @@ export function ChainConnectivityWarning() {
|
||||
{chainId === SupportedChainId.MAINNET ? (
|
||||
<Trans>You may have lost your network connection.</Trans>
|
||||
) : (
|
||||
<Trans>{label} may be down right now, or you may have lost your network connection.</Trans>
|
||||
<Trans>You may have lost your network connection, or {label} might be down right now.</Trans>
|
||||
)}{' '}
|
||||
{(info as L2ChainInfo).statusPage !== undefined && (
|
||||
<span>
|
||||
|
@ -1,235 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { YellowCard } from 'components/Card'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ArrowDownCircle, ChevronDown, ToggleLeft } from 'react-feather'
|
||||
import { ApplicationModal } from 'state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
import { switchToNetwork } from 'utils/switchToNetwork'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '../../constants/chains'
|
||||
|
||||
const BaseWrapper = css`
|
||||
position: relative;
|
||||
margin-right: 8px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
justify-self: end;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin: 0 0.5rem 0 0;
|
||||
width: initial;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 1;
|
||||
`};
|
||||
`
|
||||
const L2Wrapper = styled.div`
|
||||
${BaseWrapper}
|
||||
`
|
||||
const BaseMenuItem = css`
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
const DisabledMenuItem = styled.div`
|
||||
${BaseMenuItem}
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
cursor: auto;
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
justify-content: center;
|
||||
:hover,
|
||||
:active,
|
||||
:focus {
|
||||
color: ${({ theme }) => theme.text2};
|
||||
}
|
||||
`
|
||||
const FallbackWrapper = styled(YellowCard)`
|
||||
${BaseWrapper}
|
||||
width: auto;
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
`
|
||||
const Icon = styled.img`
|
||||
width: 16px;
|
||||
margin-right: 2px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin-right: 4px;
|
||||
|
||||
`};
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
position: absolute;
|
||||
left: 0rem;
|
||||
top: 3rem;
|
||||
z-index: 100;
|
||||
width: 237px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
|
||||
bottom: unset;
|
||||
top: 4.5em
|
||||
right: 0;
|
||||
|
||||
`};
|
||||
> {
|
||||
padding: 12px;
|
||||
}
|
||||
> :not(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
> :not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.6;
|
||||
`
|
||||
const MenuItem = styled(ExternalLink)`
|
||||
${BaseMenuItem}
|
||||
`
|
||||
const ButtonMenuItem = styled.button`
|
||||
${BaseMenuItem}
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
outline: none;
|
||||
padding: 0;
|
||||
`
|
||||
const NetworkInfo = styled.button<{ chainId: SupportedChainId }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
height: 38px;
|
||||
padding: 0.7rem;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
}
|
||||
`
|
||||
const NetworkName = styled.div<{ chainId: SupportedChainId }>`
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 0 2px 0.5px 4px;
|
||||
margin: 0 2px;
|
||||
white-space: pre;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function NetworkCard() {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
const open = useModalOpen(ApplicationModal.ARBITRUM_OPTIONS)
|
||||
const toggle = useToggleModal(ApplicationModal.ARBITRUM_OPTIONS)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
|
||||
const [implements3085, setImplements3085] = useState(false)
|
||||
useEffect(() => {
|
||||
// metamask is currently the only known implementer of this EIP
|
||||
// here we proceed w/ a noop feature check to ensure the user's version of metamask supports network switching
|
||||
// if not, we hide the UI
|
||||
if (!library?.provider?.request || !chainId || !library?.provider?.isMetaMask) {
|
||||
return
|
||||
}
|
||||
switchToNetwork({ library, chainId })
|
||||
.then((x) => x ?? setImplements3085(true))
|
||||
.catch(() => setImplements3085(false))
|
||||
}, [library, chainId])
|
||||
|
||||
const info = chainId ? CHAIN_INFO[chainId] : undefined
|
||||
if (!chainId || chainId === SupportedChainId.MAINNET || !info || !library) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (L2_CHAIN_IDS.includes(chainId)) {
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const isArbitrum = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY].includes(chainId)
|
||||
return (
|
||||
<L2Wrapper ref={node}>
|
||||
<NetworkInfo onClick={toggle} chainId={chainId}>
|
||||
<Icon src={info.logoUrl} />
|
||||
<NetworkName chainId={chainId}>{info.label}</NetworkName>
|
||||
<ChevronDown size={16} style={{ marginTop: '2px' }} strokeWidth={2.5} />
|
||||
</NetworkInfo>
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<MenuItem href={info.bridge}>
|
||||
<div>{isArbitrum ? <Trans>{info.label} Bridge</Trans> : <Trans>Optimistic L2 Gateway</Trans>}</div>
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
<MenuItem href={info.explorer}>
|
||||
{isArbitrum ? <Trans>{info.label} Explorer</Trans> : <Trans>Optimistic Etherscan</Trans>}
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
<MenuItem href={info.docs}>
|
||||
<div>
|
||||
<Trans>Learn more</Trans>
|
||||
</div>
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
{implements3085 ? (
|
||||
<ButtonMenuItem onClick={() => switchToNetwork({ library, chainId: SupportedChainId.MAINNET })}>
|
||||
<div>
|
||||
<Trans>Switch to L1 (Mainnet)</Trans>
|
||||
</div>
|
||||
<ToggleLeft opacity={0.6} size={16} />
|
||||
</ButtonMenuItem>
|
||||
) : (
|
||||
<DisabledMenuItem>
|
||||
<Trans>Change your network to go back to L1</Trans>
|
||||
</DisabledMenuItem>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</L2Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return <FallbackWrapper title={info.label}>{info.label}</FallbackWrapper>
|
||||
}
|
249
src/components/Header/NetworkSelector.tsx
Normal file
249
src/components/Header/NetworkSelector.tsx
Normal file
@ -0,0 +1,249 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ARBITRUM_HELP_CENTER_LINK,
|
||||
CHAIN_INFO,
|
||||
L2_CHAIN_IDS,
|
||||
OPTIMISM_HELP_CENTER_LINK,
|
||||
SupportedChainId,
|
||||
SupportedL2ChainId,
|
||||
} from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { ArrowDownCircle, ChevronDown } from 'react-feather'
|
||||
import { ApplicationModal } from 'state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { switchToNetwork } from 'utils/switchToNetwork'
|
||||
|
||||
const ActiveRowLinkList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 8px;
|
||||
& > a {
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
& > a:first-child {
|
||||
border-top: 1px solid ${({ theme }) => theme.text2};
|
||||
margin: 0;
|
||||
margin-top: 6px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
`
|
||||
const ActiveRowWrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
padding: 8px 0 8px 0;
|
||||
width: 100%;
|
||||
`
|
||||
const FlyoutHeader = styled.div`
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-weight: 400;
|
||||
`
|
||||
const FlyoutMenu = styled.div`
|
||||
align-items: flex-start;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
position: absolute;
|
||||
top: 64px;
|
||||
width: 272px;
|
||||
z-index: 99;
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
top: 50px;
|
||||
}
|
||||
`
|
||||
const FlyoutRow = styled.div<{ active: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ active, theme }) => (active ? theme.bg2 : 'transparent')};
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
`
|
||||
const FlyoutRowActiveIndicator = styled.div`
|
||||
background-color: ${({ theme }) => theme.green1};
|
||||
border-radius: 50%;
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
`
|
||||
const Logo = styled.img`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
const NetworkLabel = styled.div`
|
||||
flex: 1 1 auto;
|
||||
`
|
||||
const SelectorLabel = styled(NetworkLabel)`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const SelectorControls = styled.div<{ interactive: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
border: 2px solid ${({ theme }) => theme.bg1};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: ${({ interactive }) => (interactive ? 'pointer' : 'auto')};
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
`
|
||||
const SelectorLogo = styled(Logo)<{ interactive?: boolean }>`
|
||||
margin-right: ${({ interactive }) => (interactive ? 8 : 0)}px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const SelectorWrapper = styled.div`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
const StyledChevronDown = styled(ChevronDown)`
|
||||
width: 12px;
|
||||
`
|
||||
const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
|
||||
switch (chainId) {
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
case SupportedChainId.ARBITRUM_RINKEBY:
|
||||
return <Trans>Arbitrum Bridge</Trans>
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return <Trans>Optimism Gateway</Trans>
|
||||
default:
|
||||
return <Trans>Bridge</Trans>
|
||||
}
|
||||
}
|
||||
const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
|
||||
switch (chainId) {
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
case SupportedChainId.ARBITRUM_RINKEBY:
|
||||
return <Trans>Arbiscan</Trans>
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return <Trans>Optimistic Etherscan</Trans>
|
||||
default:
|
||||
return <Trans>Explorer</Trans>
|
||||
}
|
||||
}
|
||||
|
||||
export default function NetworkSelector() {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR)
|
||||
const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
const implements3085 = useAppSelector((state) => state.application.implements3085)
|
||||
|
||||
const info = chainId ? CHAIN_INFO[chainId] : undefined
|
||||
|
||||
const isOnL2 = chainId ? L2_CHAIN_IDS.includes(chainId) : false
|
||||
const showSelector = Boolean(implements3085 || isOnL2)
|
||||
const mainnetInfo = CHAIN_INFO[SupportedChainId.MAINNET]
|
||||
|
||||
const conditionalToggle = useCallback(() => {
|
||||
if (showSelector) {
|
||||
toggle()
|
||||
}
|
||||
}, [showSelector, toggle])
|
||||
|
||||
if (!chainId || !info || !library) {
|
||||
return null
|
||||
}
|
||||
|
||||
function Row({ targetChain }: { targetChain: number }) {
|
||||
if (!library || !chainId || (!implements3085 && targetChain !== chainId)) {
|
||||
return null
|
||||
}
|
||||
const handleRowClick = () => {
|
||||
switchToNetwork({ library, chainId: targetChain })
|
||||
toggle()
|
||||
}
|
||||
const active = chainId === targetChain
|
||||
const hasExtendedInfo = L2_CHAIN_IDS.includes(targetChain)
|
||||
const isOptimism = targetChain === SupportedChainId.OPTIMISM
|
||||
const rowText = `${CHAIN_INFO[targetChain].label}${isOptimism ? ' (Optimism)' : ''}`
|
||||
const RowContent = () => (
|
||||
<FlyoutRow onClick={handleRowClick} active={active}>
|
||||
<Logo src={CHAIN_INFO[targetChain].logoUrl} />
|
||||
<NetworkLabel>{rowText}</NetworkLabel>
|
||||
{chainId === targetChain && <FlyoutRowActiveIndicator />}
|
||||
</FlyoutRow>
|
||||
)
|
||||
const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
|
||||
if (active && hasExtendedInfo) {
|
||||
return (
|
||||
<ActiveRowWrapper>
|
||||
<RowContent />
|
||||
<ActiveRowLinkList>
|
||||
<ExternalLink href={CHAIN_INFO[targetChain as SupportedL2ChainId].bridge}>
|
||||
<BridgeText chainId={chainId} /> <LinkOutCircle />
|
||||
</ExternalLink>
|
||||
<ExternalLink href={CHAIN_INFO[targetChain].explorer}>
|
||||
<ExplorerText chainId={chainId} /> <LinkOutCircle />
|
||||
</ExternalLink>
|
||||
<ExternalLink href={helpCenterLink}>
|
||||
<Trans>Help Center</Trans> <LinkOutCircle />
|
||||
</ExternalLink>
|
||||
</ActiveRowLinkList>
|
||||
</ActiveRowWrapper>
|
||||
)
|
||||
}
|
||||
return <RowContent />
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectorWrapper ref={node as any}>
|
||||
<SelectorControls onClick={conditionalToggle} interactive={showSelector}>
|
||||
<SelectorLogo interactive={showSelector} src={info.logoUrl || mainnetInfo.logoUrl} />
|
||||
<SelectorLabel>{info.label}</SelectorLabel>
|
||||
{showSelector && <StyledChevronDown />}
|
||||
</SelectorControls>
|
||||
{open && (
|
||||
<FlyoutMenu>
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a network</Trans>
|
||||
</FlyoutHeader>
|
||||
<Row targetChain={SupportedChainId.MAINNET} />
|
||||
<Row targetChain={SupportedChainId.OPTIMISM} />
|
||||
<Row targetChain={SupportedChainId.ARBITRUM_ONE} />
|
||||
</FlyoutMenu>
|
||||
)}
|
||||
</SelectorWrapper>
|
||||
)
|
||||
}
|
@ -22,7 +22,7 @@ import Modal from '../Modal'
|
||||
import Row from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import Web3Status from '../Web3Status'
|
||||
import NetworkCard from './NetworkCard'
|
||||
import NetworkSelector from './NetworkSelector'
|
||||
import UniBalanceContent from './UniBalanceContent'
|
||||
|
||||
const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
@ -72,6 +72,10 @@ const HeaderElement = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
/* addresses safari's lack of support for "gap" */
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
@ -300,7 +304,9 @@ export default function Header() {
|
||||
</HeaderLinks>
|
||||
|
||||
<HeaderControls>
|
||||
<NetworkCard />
|
||||
<HeaderElement>
|
||||
<NetworkSelector />
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
{availableClaim && !showClaimPopup && (
|
||||
<UNIWrapper onClick={toggleClaimModal}>
|
||||
@ -326,6 +332,8 @@ export default function Header() {
|
||||
) : null}
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
<Menu />
|
||||
</HeaderElement>
|
||||
</HeaderControls>
|
||||
|
@ -62,7 +62,6 @@ const UNIbutton = styled(ButtonPrimary)`
|
||||
`
|
||||
|
||||
const StyledMenu = styled.div`
|
||||
margin-left: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -1,134 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ArbitrumWrapperBackgroundDarkMode,
|
||||
ArbitrumWrapperBackgroundLightMode,
|
||||
OptimismWrapperBackgroundDarkMode,
|
||||
OptimismWrapperBackgroundLightMode,
|
||||
} from 'components/NetworkAlert/NetworkAlert'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { ReadMoreLink } from './styles'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
display: none;
|
||||
height: 40px;
|
||||
margin: auto 20px auto 4px;
|
||||
width: 40px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
flex-direction: row;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 143%;
|
||||
margin: 12px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
flex: 1 1 auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
max-height: 47px;
|
||||
padding: 16px 12px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
margin: auto 0 auto auto;
|
||||
padding: 14px 16px;
|
||||
min-width: 226px;
|
||||
}
|
||||
`
|
||||
export function AddLiquidityNetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
|
||||
const readMoreLink = isOptimism
|
||||
? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism'
|
||||
: 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum'
|
||||
return (
|
||||
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Body>
|
||||
<Trans>This is an alpha release of Uniswap on the {info.label} network.</Trans>
|
||||
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>{' '}
|
||||
<ReadMoreLink href={readMoreLink}>
|
||||
<Trans>Read more</Trans>
|
||||
</ReadMoreLink>
|
||||
</Body>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {info.label}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ArbitrumWrapperBackgroundDarkMode,
|
||||
ArbitrumWrapperBackgroundLightMode,
|
||||
OptimismWrapperBackgroundDarkMode,
|
||||
OptimismWrapperBackgroundLightMode,
|
||||
} from 'components/NetworkAlert/NetworkAlert'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { ReadMoreLink } from './styles'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
display: none;
|
||||
height: 40px;
|
||||
margin: auto 20px auto 4px;
|
||||
width: 40px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex-direction: row;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 143%;
|
||||
margin: 12px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex: 1 1 auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
max-height: 47px;
|
||||
padding: 16px 8px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
margin: auto 0 auto auto;
|
||||
padding: 14px 17px;
|
||||
min-width: 226px;
|
||||
}
|
||||
`
|
||||
export function MinimalNetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
|
||||
const readMoreLink = isOptimism
|
||||
? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism'
|
||||
: 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum'
|
||||
return (
|
||||
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Body>
|
||||
<Trans>This is an alpha release of Uniswap on the {info.label} network.</Trans>
|
||||
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>{' '}
|
||||
<ReadMoreLink href={readMoreLink}>
|
||||
<Trans>Read more</Trans>
|
||||
</ReadMoreLink>
|
||||
</Body>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {info.label}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
@ -1,27 +1,74 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import {
|
||||
ARBITRUM_HELP_CENTER_LINK,
|
||||
L2_CHAIN_IDS,
|
||||
OPTIMISM_HELP_CENTER_LINK,
|
||||
SupportedChainId,
|
||||
SupportedL2ChainId,
|
||||
} from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownCircle, X } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager, useOptimismAlphaAlert } from 'state/user/hooks'
|
||||
import { useETHBalances } from 'state/wallet/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { CHAIN_INFO } from '../../constants/chains'
|
||||
import { ReadMoreLink } from './styles'
|
||||
|
||||
export const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
const L2Icon = styled.img`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
justify-self: center;
|
||||
`
|
||||
const BetaTag = styled.span<{ color: string }>`
|
||||
align-items: center;
|
||||
background-color: ${({ color }) => color};
|
||||
border-radius: 6px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 28px;
|
||||
justify-content: center;
|
||||
left: -16px;
|
||||
position: absolute;
|
||||
transform: rotate(-15deg);
|
||||
top: -16px;
|
||||
width: 60px;
|
||||
z-index: 1;
|
||||
`
|
||||
const Body = styled.p`
|
||||
font-size: 12px;
|
||||
grid-column: 1 / 3;
|
||||
line-height: 143%;
|
||||
margin: 0;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
`
|
||||
export const Controls = styled.div<{ thin?: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
${({ thin }) =>
|
||||
thin &&
|
||||
css`
|
||||
margin: auto 32px auto 0;
|
||||
`}
|
||||
`
|
||||
const CloseIcon = styled(X)`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
const ContentWrapper = styled.div`
|
||||
const BodyText = styled.div`
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-gap: 4px;
|
||||
@ -33,6 +80,37 @@ const ContentWrapper = styled.div`
|
||||
grid-gap: 8px;
|
||||
}
|
||||
`
|
||||
const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 8px;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
justify-content: space-between;
|
||||
margin: 0 0 20px 0;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
transition: background-color 150ms ease-in-out;
|
||||
${({ thin }) =>
|
||||
thin &&
|
||||
css`
|
||||
font-size: 14px;
|
||||
margin: auto;
|
||||
width: 112px;
|
||||
`}
|
||||
`
|
||||
const RootWrapper = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
export const ArbitrumWrapperBackgroundDarkMode = css`
|
||||
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
|
||||
radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1);
|
||||
@ -49,7 +127,7 @@ export const OptimismWrapperBackgroundLightMode = css`
|
||||
background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),
|
||||
radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5);
|
||||
`
|
||||
const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
|
||||
const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string; thin?: boolean }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
@ -66,7 +144,13 @@ const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; l
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
${({ thin }) =>
|
||||
thin &&
|
||||
css`
|
||||
flex-direction: row;
|
||||
max-width: max-content;
|
||||
min-height: min-content;
|
||||
`}
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
@ -80,36 +164,29 @@ const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; l
|
||||
z-index: -1;
|
||||
}
|
||||
`
|
||||
const Header = styled.h2`
|
||||
const Header = styled.h2<{ thin?: boolean }>`
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
padding-right: 30px;
|
||||
`
|
||||
const Body = styled.p`
|
||||
font-size: 12px;
|
||||
grid-column: 1 / 3;
|
||||
line-height: 143%;
|
||||
margin: 0;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
display: ${({ thin }) => (thin ? 'none' : 'block')};
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
margin-left: 12px;
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
justify-content: space-between;
|
||||
margin: 0 20px 20px 20px;
|
||||
margin: 0 12px 20px 18px;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
@ -118,52 +195,85 @@ const LinkOutToBridge = styled(ExternalLink)`
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
${({ thin }) =>
|
||||
thin &&
|
||||
css`
|
||||
font-size: 14px;
|
||||
margin: auto 10px;
|
||||
width: 168px;
|
||||
`}
|
||||
`
|
||||
export function NetworkAlert() {
|
||||
|
||||
interface NetworkAlertProps {
|
||||
thin?: boolean
|
||||
}
|
||||
|
||||
export function NetworkAlert(props: NetworkAlertProps) {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
const [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged] = useOptimismAlphaAlert()
|
||||
const [locallyDismissed, setLocallyDimissed] = useState(false)
|
||||
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
if (userEthBalance?.greaterThan(0)) {
|
||||
setArbitrumAlphaAcknowledged(true)
|
||||
switch (chainId) {
|
||||
case SupportedChainId.OPTIMISM:
|
||||
setOptimismAlphaAcknowledged(true)
|
||||
break
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
setArbitrumAlphaAcknowledged(true)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
setLocallyDimissed(true)
|
||||
}
|
||||
}, [setArbitrumAlphaAcknowledged, userEthBalance])
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged || locallyDismissed) {
|
||||
}, [chainId, setArbitrumAlphaAcknowledged, setOptimismAlphaAcknowledged, userEthBalance])
|
||||
|
||||
const onOptimismAndOptimismAcknowledged = SupportedChainId.OPTIMISM === chainId && optimismAlphaAcknowledged
|
||||
const onArbitrumAndArbitrumAcknowledged = SupportedChainId.ARBITRUM_ONE === chainId && arbitrumAlphaAcknowledged
|
||||
if (
|
||||
!chainId ||
|
||||
!L2_CHAIN_IDS.includes(chainId) ||
|
||||
onArbitrumAndArbitrumAcknowledged ||
|
||||
onOptimismAndOptimismAcknowledged ||
|
||||
locallyDismissed
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
|
||||
const readMoreLink = isOptimism
|
||||
? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism'
|
||||
: 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum'
|
||||
const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
|
||||
const showCloseIcon = Boolean(userEthBalance?.greaterThan(0) && !props.thin)
|
||||
return (
|
||||
<RootWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl}>
|
||||
<CloseIcon onClick={dismiss} />
|
||||
<ContentWrapper>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Header>
|
||||
<Trans>Uniswap on {info.label}</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>
|
||||
This is an alpha release of Uniswap on the {info.label} network. You must bridge L1 assets to the network to
|
||||
swap them.
|
||||
</Trans>{' '}
|
||||
<ReadMoreLink href={readMoreLink}>
|
||||
<Trans>Read more</Trans>
|
||||
</ReadMoreLink>
|
||||
</Body>
|
||||
<RootWrapper>
|
||||
<BetaTag color={isOptimism ? '#ff0420' : '#0490ed'}>Beta</BetaTag>
|
||||
<ContentWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl} thin={props.thin}>
|
||||
{showCloseIcon && <CloseIcon onClick={dismiss} />}
|
||||
<BodyText>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Header thin={props.thin}>
|
||||
<Trans>Uniswap on {info.label}</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>
|
||||
To starting trading on {info.label}, first bridge your assets from L1 to L2. Please treat this as a beta
|
||||
release and learn about the risks before using {info.label}.
|
||||
</Trans>
|
||||
</Body>
|
||||
</BodyText>
|
||||
<Controls thin={props.thin}>
|
||||
<LinkOutToBridge href={depositUrl} thin={props.thin}>
|
||||
<Trans>Deposit Assets</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
<LearnMoreLink href={helpCenterLink} thin={props.thin}>
|
||||
<Trans>Learn More</Trans>
|
||||
</LearnMoreLink>
|
||||
</Controls>
|
||||
</ContentWrapper>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {info.label}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</RootWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
|
||||
export const ReadMoreLink = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.text1};
|
||||
text-decoration: underline;
|
||||
`
|
@ -1,63 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
|
||||
const Root = styled.div`
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
border-radius: 18px;
|
||||
color: black;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
max-width: 880px;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
margin: 0 8px 0 0;
|
||||
`
|
||||
const TitleRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 25px;
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
margin: 8px 0 0 0;
|
||||
`
|
||||
const ReadMoreLink = styled(ExternalLink)`
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
`
|
||||
|
||||
export default function OptimismDowntimeWarning() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
if (!chainId || ![SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<TitleRow>
|
||||
<WarningIcon />
|
||||
<Trans>Optimism Planned Downtime</Trans>
|
||||
</TitleRow>
|
||||
<Body>
|
||||
<Trans>
|
||||
Optimism expects planned downtime in the near future. Unplanned downtime may also occur. While the network is
|
||||
down, fees will not be generated and you will be unable to remove liquidity.{' '}
|
||||
<ReadMoreLink href="https://help.uniswap.org/en/articles/5406082-what-happens-if-the-optimistic-ethereum-network-experiences-downtime">
|
||||
Read more.
|
||||
</ReadMoreLink>
|
||||
</Trans>
|
||||
</Body>
|
||||
</Root>
|
||||
)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
|
||||
import optimismLogoUrl from 'assets/svg/optimism_logo.svg'
|
||||
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
|
||||
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
|
||||
import ms from 'ms.macro'
|
||||
|
||||
export enum SupportedChainId {
|
||||
@ -47,12 +48,19 @@ export const L2_CHAIN_IDS = [
|
||||
|
||||
export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number]
|
||||
|
||||
interface L1ChainInfo {
|
||||
export interface L1ChainInfo {
|
||||
readonly blockWaitMsBeforeWarning?: number
|
||||
readonly docs: string
|
||||
readonly explorer: string
|
||||
readonly infoLink: string
|
||||
readonly label: string
|
||||
readonly logoUrl?: string
|
||||
readonly rpcUrls?: string[]
|
||||
readonly nativeCurrency: {
|
||||
name: string // 'Goerli ETH',
|
||||
symbol: string // 'gorETH',
|
||||
decimals: number //18,
|
||||
}
|
||||
}
|
||||
export interface L2ChainInfo extends L1ChainInfo {
|
||||
readonly bridge: string
|
||||
@ -60,7 +68,7 @@ export interface L2ChainInfo extends L1ChainInfo {
|
||||
readonly statusPage?: string
|
||||
}
|
||||
|
||||
type ChainInfo = { readonly [chainId: number]: L1ChainInfo | L2ChainInfo } & {
|
||||
export type ChainInfo = { readonly [chainId: number]: L1ChainInfo | L2ChainInfo } & {
|
||||
readonly [chainId in SupportedL2ChainId]: L2ChainInfo
|
||||
} &
|
||||
{ readonly [chainId in SupportedL1ChainId]: L1ChainInfo }
|
||||
@ -74,6 +82,8 @@ export const CHAIN_INFO: ChainInfo = {
|
||||
infoLink: 'https://info.uniswap.org/#/arbitrum',
|
||||
label: 'Arbitrum',
|
||||
logoUrl: arbitrumLogoUrl,
|
||||
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrls: ['https://arb1.arbitrum.io/rpc'],
|
||||
},
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: {
|
||||
blockWaitMsBeforeWarning: ms`10m`,
|
||||
@ -83,45 +93,55 @@ export const CHAIN_INFO: ChainInfo = {
|
||||
infoLink: 'https://info.uniswap.org/#/arbitrum/',
|
||||
label: 'Arbitrum Rinkeby',
|
||||
logoUrl: arbitrumLogoUrl,
|
||||
nativeCurrency: { name: 'Rinkeby ArbETH', symbol: 'rinkArbETH', decimals: 18 },
|
||||
rpcUrls: ['https://rinkeby.arbitrum.io/rpc'],
|
||||
},
|
||||
[SupportedChainId.MAINNET]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Mainnet',
|
||||
label: 'Ethereum',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.RINKEBY]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://rinkeby.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Rinkeby',
|
||||
nativeCurrency: { name: 'Rinkeby ETH', symbol: 'rinkETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.ROPSTEN]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://ropsten.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Ropsten',
|
||||
nativeCurrency: { name: 'Ropsten ETH', symbol: 'ropETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.KOVAN]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://kovan.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Kovan',
|
||||
nativeCurrency: { name: 'Kovan ETH', symbol: 'kovETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.GOERLI]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://goerli.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Görli',
|
||||
nativeCurrency: { name: 'Görli ETH', symbol: 'görETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.OPTIMISM]: {
|
||||
blockWaitMsBeforeWarning: ms`10m`,
|
||||
bridge: 'https://gateway.optimism.io/',
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/optimism/',
|
||||
label: 'Optimism',
|
||||
infoLink: 'https://info.uniswap.org/#/optimism',
|
||||
label: 'OΞ',
|
||||
logoUrl: optimismLogoUrl,
|
||||
nativeCurrency: { name: 'Optimistic ETH', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrls: ['https://mainnet.optimism.io'],
|
||||
statusPage: 'https://optimism.io/status',
|
||||
},
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: {
|
||||
@ -131,7 +151,13 @@ export const CHAIN_INFO: ChainInfo = {
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/optimism',
|
||||
label: 'Optimistic Kovan',
|
||||
rpcUrls: ['https://kovan.optimism.io'],
|
||||
logoUrl: optimismLogoUrl,
|
||||
nativeCurrency: { name: 'Optimistic kovETH', symbol: 'kovOpETH', decimals: 18 },
|
||||
statusPage: 'https://optimism.io/status',
|
||||
},
|
||||
}
|
||||
|
||||
export const ARBITRUM_HELP_CENTER_LINK = 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum'
|
||||
export const OPTIMISM_HELP_CENTER_LINK =
|
||||
'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ'
|
||||
|
@ -28,7 +28,6 @@ import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallbac
|
||||
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { Field, Bound } from '../../state/mint/v3/actions'
|
||||
import { AddLiquidityNetworkAlert } from 'components/NetworkAlert/AddLiquidityNetworkAlert'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
|
||||
import { TYPE, ExternalLink } from '../../theme'
|
||||
@ -71,7 +70,7 @@ import HoverInlineText from 'components/HoverInlineText'
|
||||
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
|
||||
import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import OptimismDowntimeWarning from 'components/OptimismDowntimeWarning'
|
||||
import DowntimeWarning from 'components/DowntimeWarning'
|
||||
import { CHAIN_INFO } from '../../constants/chains'
|
||||
|
||||
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
|
||||
@ -550,8 +549,7 @@ export default function AddLiquidity({
|
||||
return (
|
||||
<>
|
||||
<ScrollablePage>
|
||||
<AddLiquidityNetworkAlert />
|
||||
<OptimismDowntimeWarning />
|
||||
<DowntimeWarning />
|
||||
<TransactionConfirmationModal
|
||||
isOpen={showConfirm}
|
||||
onDismiss={handleDismissConfirmation}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { MinimalNetworkAlert } from 'components/NetworkAlert/MinimalNetworkAlert'
|
||||
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import styled from 'styled-components/macro'
|
||||
@ -127,30 +126,27 @@ export default function CTACards() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
return (
|
||||
<div>
|
||||
<MinimalNetworkAlert />
|
||||
<CTASection>
|
||||
<CTA1 href={'https://help.uniswap.org/en/articles/5391541-providing-liquidity-on-uniswap-v3'}>
|
||||
<ResponsiveColumn>
|
||||
<HeaderText>
|
||||
<Trans>Learn about providing liquidity</Trans> ↗
|
||||
</HeaderText>
|
||||
<TYPE.body fontWeight={300} style={{ alignItems: 'center', display: 'flex', maxWidth: '80%' }}>
|
||||
<Trans>Check out our v3 LP walkthrough and migration guides.</Trans>
|
||||
</TYPE.body>
|
||||
</ResponsiveColumn>
|
||||
</CTA1>
|
||||
<CTA2 href={infoLink + 'pools'}>
|
||||
<ResponsiveColumn>
|
||||
<HeaderText style={{ alignSelf: 'flex-start' }}>
|
||||
<Trans>Top pools</Trans> ↗
|
||||
</HeaderText>
|
||||
<TYPE.body fontWeight={300} style={{ alignSelf: 'flex-start' }}>
|
||||
<Trans>Explore popular pools on Uniswap Analytics.</Trans>
|
||||
</TYPE.body>
|
||||
</ResponsiveColumn>
|
||||
</CTA2>
|
||||
</CTASection>
|
||||
</div>
|
||||
<CTASection>
|
||||
<CTA1 href={'https://help.uniswap.org/en/articles/5391541-providing-liquidity-on-uniswap-v3'}>
|
||||
<ResponsiveColumn>
|
||||
<HeaderText>
|
||||
<Trans>Learn about providing liquidity</Trans> ↗
|
||||
</HeaderText>
|
||||
<TYPE.body fontWeight={300} style={{ alignItems: 'center', display: 'flex', maxWidth: '80%' }}>
|
||||
<Trans>Check out our v3 LP walkthrough and migration guides.</Trans>
|
||||
</TYPE.body>
|
||||
</ResponsiveColumn>
|
||||
</CTA1>
|
||||
<CTA2 href={infoLink + 'pools'}>
|
||||
<ResponsiveColumn>
|
||||
<HeaderText style={{ alignSelf: 'flex-start' }}>
|
||||
<Trans>Top pools</Trans> ↗
|
||||
</HeaderText>
|
||||
<TYPE.body fontWeight={300} style={{ alignSelf: 'flex-start' }}>
|
||||
<Trans>Explore popular pools on Uniswap Analytics.</Trans>
|
||||
</TYPE.body>
|
||||
</ResponsiveColumn>
|
||||
</CTA2>
|
||||
</CTASection>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ButtonGray, ButtonOutlined, ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import DowntimeWarning from 'components/DowntimeWarning'
|
||||
import { FlyoutAlignment, NewMenu } from 'components/Menu'
|
||||
import { SwapPoolTabs } from 'components/NavigationTabs'
|
||||
import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert'
|
||||
import PositionList from 'components/PositionList'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
|
||||
@ -221,6 +223,8 @@ export default function Pool() {
|
||||
</TitleRow>
|
||||
|
||||
<HideSmall>
|
||||
<NetworkAlert thin />
|
||||
<DowntimeWarning />
|
||||
<CTACards />
|
||||
</HideSmall>
|
||||
|
||||
|
@ -18,7 +18,7 @@ export enum ApplicationModal {
|
||||
DELEGATE,
|
||||
VOTE,
|
||||
POOL_OVERVIEW_OPTIONS,
|
||||
ARBITRUM_OPTIONS,
|
||||
NETWORK_SELECTOR,
|
||||
}
|
||||
|
||||
export const updateChainId = createAction<{ chainId: number | null }>('application/updateChainId')
|
||||
@ -27,4 +27,5 @@ export const setOpenModal = createAction<ApplicationModal | null>('application/s
|
||||
export const addPopup =
|
||||
createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>('application/addPopup')
|
||||
export const removePopup = createAction<{ key: string }>('application/removePopup')
|
||||
export const setImplements3085 = createAction<{ implements3085: boolean }>('application/setImplements3085')
|
||||
export const setChainConnectivityWarning = createAction<{ warn: boolean }>('application/setChainConnectivityWarning')
|
||||
|
@ -2,32 +2,34 @@ import { createReducer, nanoid } from '@reduxjs/toolkit'
|
||||
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
|
||||
import {
|
||||
addPopup,
|
||||
ApplicationModal,
|
||||
PopupContent,
|
||||
removePopup,
|
||||
updateBlockNumber,
|
||||
ApplicationModal,
|
||||
setOpenModal,
|
||||
updateChainId,
|
||||
setChainConnectivityWarning,
|
||||
setImplements3085,
|
||||
setOpenModal,
|
||||
updateBlockNumber,
|
||||
updateChainId,
|
||||
} from './actions'
|
||||
|
||||
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
|
||||
|
||||
export interface ApplicationState {
|
||||
// used by RTK-Query to build dynamic subgraph urls
|
||||
readonly chainId: number | null
|
||||
readonly chainConnectivityWarning: boolean
|
||||
readonly blockNumber: { readonly [chainId: number]: number }
|
||||
readonly popupList: PopupList
|
||||
readonly chainConnectivityWarning: boolean
|
||||
readonly chainId: number | null
|
||||
readonly implements3085: boolean
|
||||
readonly openModal: ApplicationModal | null
|
||||
readonly popupList: PopupList
|
||||
}
|
||||
|
||||
const initialState: ApplicationState = {
|
||||
chainId: null,
|
||||
chainConnectivityWarning: false,
|
||||
blockNumber: {},
|
||||
popupList: [],
|
||||
chainConnectivityWarning: false,
|
||||
chainId: null,
|
||||
implements3085: false,
|
||||
openModal: null,
|
||||
popupList: [],
|
||||
}
|
||||
|
||||
export default createReducer(initialState, (builder) =>
|
||||
@ -64,6 +66,9 @@ export default createReducer(initialState, (builder) =>
|
||||
}
|
||||
})
|
||||
})
|
||||
.addCase(setImplements3085, (state, { payload: { implements3085 } }) => {
|
||||
state.implements3085 = implements3085
|
||||
})
|
||||
.addCase(setChainConnectivityWarning, (state, { payload: { warn } }) => {
|
||||
state.chainConnectivityWarning = warn
|
||||
})
|
||||
|
@ -7,7 +7,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { api, CHAIN_TAG } from 'state/data/enhanced'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { supportedChainId } from 'utils/supportedChainId'
|
||||
import { setChainConnectivityWarning, updateBlockNumber, updateChainId } from './actions'
|
||||
import { switchToNetwork } from 'utils/switchToNetwork'
|
||||
import { setChainConnectivityWarning, setImplements3085, updateBlockNumber, updateChainId } from './actions'
|
||||
import { useBlockNumber } from './hooks'
|
||||
|
||||
function useQueryCacheInvalidator() {
|
||||
@ -61,7 +62,7 @@ function useBlockWarningTimer() {
|
||||
}
|
||||
|
||||
export default function Updater(): null {
|
||||
const { library, chainId } = useActiveWeb3React()
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const dispatch = useAppDispatch()
|
||||
const windowVisible = useIsWindowVisible()
|
||||
|
||||
@ -116,5 +117,14 @@ export default function Updater(): null {
|
||||
)
|
||||
}, [dispatch, debouncedState.chainId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!account || !library?.provider?.request || !library?.provider?.isMetaMask) {
|
||||
return
|
||||
}
|
||||
switchToNetwork({ library })
|
||||
.then((x) => x ?? dispatch(setImplements3085({ implements3085: true })))
|
||||
.catch(() => dispatch(setImplements3085({ implements3085: false })))
|
||||
}, [account, chainId, dispatch, library])
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>(
|
||||
export const updateArbitrumAlphaAcknowledged = createAction<{ arbitrumAlphaAcknowledged: boolean }>(
|
||||
'user/updateArbitrumAlphaAcknowledged'
|
||||
)
|
||||
export const updateOptimismAlphaAcknowledged = createAction<{ optimismAlphaAcknowledged: boolean }>(
|
||||
'user/updateOptimismAlphaAcknowledged'
|
||||
)
|
||||
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode')
|
||||
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
|
||||
export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale')
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
SerializedToken,
|
||||
updateArbitrumAlphaAcknowledged,
|
||||
updateHideClosedPositions,
|
||||
updateOptimismAlphaAcknowledged,
|
||||
updateUserDarkMode,
|
||||
updateUserDeadline,
|
||||
updateUserExpertMode,
|
||||
@ -353,3 +354,13 @@ export function useArbitrumAlphaAlert(): [boolean, (arbitrumAlphaAcknowledged: b
|
||||
|
||||
return [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged]
|
||||
}
|
||||
|
||||
export function useOptimismAlphaAlert(): [boolean, (optimismAlphaAcknowledged: boolean) => void] {
|
||||
const dispatch = useAppDispatch()
|
||||
const optimismAlphaAcknowledged = useAppSelector(({ user }) => user.optimismAlphaAcknowledged)
|
||||
const setOptimismAlphaAcknowledged = (optimismAlphaAcknowledged: boolean) => {
|
||||
dispatch(updateOptimismAlphaAcknowledged({ optimismAlphaAcknowledged }))
|
||||
}
|
||||
|
||||
return [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged]
|
||||
}
|
||||
|
@ -12,11 +12,12 @@ import {
|
||||
updateArbitrumAlphaAcknowledged,
|
||||
updateHideClosedPositions,
|
||||
updateMatchesDarkMode,
|
||||
updateOptimismAlphaAcknowledged,
|
||||
updateUserClientSideRouter,
|
||||
updateUserDarkMode,
|
||||
updateUserDeadline,
|
||||
updateUserExpertMode,
|
||||
updateUserLocale,
|
||||
updateUserClientSideRouter,
|
||||
updateUserSlippageTolerance,
|
||||
} from './actions'
|
||||
|
||||
@ -28,9 +29,10 @@ export interface UserState {
|
||||
// the timestamp of the last updateVersion action
|
||||
lastUpdateVersionTimestamp?: number
|
||||
|
||||
userDarkMode: boolean | null // the user's choice for dark mode or light mode
|
||||
matchesDarkMode: boolean // whether the dark mode media query matches
|
||||
optimismAlphaAcknowledged: boolean
|
||||
|
||||
userDarkMode: boolean | null // the user's choice for dark mode or light mode
|
||||
userLocale: SupportedLocale | null
|
||||
|
||||
userExpertMode: boolean
|
||||
@ -70,8 +72,9 @@ function pairKey(token0Address: string, token1Address: string) {
|
||||
|
||||
export const initialState: UserState = {
|
||||
arbitrumAlphaAcknowledged: false,
|
||||
userDarkMode: null,
|
||||
matchesDarkMode: false,
|
||||
optimismAlphaAcknowledged: false,
|
||||
userDarkMode: null,
|
||||
userExpertMode: false,
|
||||
userLocale: null,
|
||||
userClientSideRouter: false,
|
||||
@ -131,6 +134,9 @@ export default createReducer(initialState, (builder) =>
|
||||
.addCase(updateArbitrumAlphaAcknowledged, (state, action) => {
|
||||
state.arbitrumAlphaAcknowledged = action.payload.arbitrumAlphaAcknowledged
|
||||
})
|
||||
.addCase(updateOptimismAlphaAcknowledged, (state, action) => {
|
||||
state.optimismAlphaAcknowledged = action.payload.optimismAlphaAcknowledged
|
||||
})
|
||||
.addCase(updateUserExpertMode, (state, action) => {
|
||||
state.userExpertMode = action.payload.userExpertMode
|
||||
state.timestamp = currentTimestamp()
|
||||
|
@ -37,6 +37,7 @@ const black = '#000000'
|
||||
|
||||
function colors(darkMode: boolean): Colors {
|
||||
return {
|
||||
darkMode,
|
||||
// base
|
||||
white,
|
||||
black,
|
||||
|
2
src/theme/styled.d.ts
vendored
2
src/theme/styled.d.ts
vendored
@ -2,6 +2,8 @@ import { FlattenSimpleInterpolation, ThemedCssFunction } from 'styled-components
|
||||
|
||||
export type Color = string
|
||||
export interface Colors {
|
||||
darkMode: boolean
|
||||
|
||||
// base
|
||||
white: Color
|
||||
black: Color
|
||||
|
34
src/utils/addNetwork.ts
Normal file
34
src/utils/addNetwork.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { L1ChainInfo, L2ChainInfo, SupportedChainId } from 'constants/chains'
|
||||
import { BigNumber, utils } from 'ethers'
|
||||
|
||||
interface AddNetworkArguments {
|
||||
library: Web3Provider
|
||||
chainId: SupportedChainId
|
||||
info: L1ChainInfo | L2ChainInfo
|
||||
}
|
||||
|
||||
// provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw
|
||||
// see https://github.com/rekmarks/EIPs/blob/3326-create/EIPS/eip-3326.md for more info on wallet_switchEthereumChain
|
||||
export async function addNetwork({ library, chainId, info }: AddNetworkArguments): Promise<null | void> {
|
||||
if (!library?.provider?.request) {
|
||||
return
|
||||
}
|
||||
const formattedChainId = utils.hexStripZeros(BigNumber.from(chainId).toHexString())
|
||||
try {
|
||||
await library?.provider.request({
|
||||
method: 'wallet_addEthereumChain',
|
||||
params: [
|
||||
{
|
||||
chainId: formattedChainId,
|
||||
chainName: info.label,
|
||||
rpcUrls: info.rpcUrls,
|
||||
nativeCurrency: info.nativeCurrency,
|
||||
blockExplorerUrls: [info.explorer],
|
||||
},
|
||||
],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('error adding eth network: ', chainId, info, error)
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { BigNumber, utils } from 'ethers'
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { BigNumber, utils } from 'ethers'
|
||||
import { addNetwork } from './addNetwork'
|
||||
|
||||
interface SwitchNetworkArguments {
|
||||
library: Web3Provider
|
||||
chainId: SupportedChainId
|
||||
chainId?: SupportedChainId
|
||||
}
|
||||
|
||||
// provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw
|
||||
@ -13,9 +14,27 @@ export async function switchToNetwork({ library, chainId }: SwitchNetworkArgumen
|
||||
if (!library?.provider?.request) {
|
||||
return
|
||||
}
|
||||
if (!chainId && library?.getNetwork) {
|
||||
;({ chainId } = await library.getNetwork())
|
||||
}
|
||||
const formattedChainId = utils.hexStripZeros(BigNumber.from(chainId).toHexString())
|
||||
return library?.provider.request({
|
||||
method: 'wallet_switchEthereumChain',
|
||||
params: [{ chainId: formattedChainId }],
|
||||
})
|
||||
try {
|
||||
await library?.provider.request({
|
||||
method: 'wallet_switchEthereumChain',
|
||||
params: [{ chainId: formattedChainId }],
|
||||
})
|
||||
} catch (error) {
|
||||
// 4902 is the error code for attempting to switch to an unrecognized chainId
|
||||
if (error.code === 4902 && chainId !== undefined) {
|
||||
const info = CHAIN_INFO[chainId]
|
||||
|
||||
// metamask (only known implementer) automatically switches after a network is added
|
||||
// the second call is done here because that behavior is not a part of the spec and cannot be relied upon in the future
|
||||
// metamask's behavior when switching to the current network is just to return null (a no-op)
|
||||
await addNetwork({ library, chainId, info })
|
||||
await switchToNetwork({ library, chainId })
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user