Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
283479f76e | ||
|
|
d3c30e2f6b | ||
|
|
32d226f78e | ||
|
|
96744505c0 | ||
|
|
97236033d4 | ||
|
|
86e62dc4b9 | ||
|
|
e584a5fa36 | ||
|
|
332ef6e6c8 | ||
|
|
8cbd111e65 | ||
|
|
55ffcbd465 | ||
|
|
404775e86d | ||
|
|
0ae9fe28a2 | ||
|
|
89c0caae43 | ||
|
|
c8086e3c76 | ||
|
|
1c2842e5a0 | ||
|
|
a2c6d3f475 | ||
|
|
841ea7f8a1 | ||
|
|
804692b114 | ||
|
|
6282298d13 | ||
|
|
7a5b855097 | ||
|
|
c9908748cf | ||
|
|
79b77deee1 | ||
|
|
a554af6670 | ||
|
|
1843f214b1 | ||
|
|
3e0788092e | ||
|
|
d14c49df0d | ||
|
|
c098ad1ffe | ||
|
|
48114ef51d | ||
|
|
cb7132ee17 | ||
|
|
0fa4859a09 | ||
|
|
f8bb5046f0 | ||
|
|
7d1589d1df | ||
|
|
26b603cc2e | ||
|
|
ece68a0ec7 | ||
|
|
fd212477ce | ||
|
|
a16d2387cc | ||
|
|
cae56ec385 | ||
|
|
d16b3473e0 | ||
|
|
f66f249dba | ||
|
|
08afd888d0 | ||
|
|
b427be2673 | ||
|
|
f753a5e325 | ||
|
|
46d9d8e3df | ||
|
|
680d3a3f26 | ||
|
|
e4c625ee71 |
6
.env
6
.env
@@ -1,7 +1,11 @@
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_AWS_API_REGION="us-east-2"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
||||
ESLINT_NO_DEV_ERRORS=true
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
||||
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkStaging?platform=web"
|
||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
||||
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLink?platform=web"
|
||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
|
||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('Testing nfts', () => {
|
||||
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('exist')
|
||||
})
|
||||
|
||||
it('should be able to open bag and open sweep', () => {
|
||||
xit('should be able to open bag and open sweep', () => {
|
||||
cy.get(getTestSelector('nft-sweep-button')).first().click()
|
||||
cy.get(getTestSelector('nft-empty-bag')).should('exist')
|
||||
cy.get(getTestSelector('nft-sweep-slider')).should('exist')
|
||||
@@ -52,7 +52,7 @@ describe('Testing nfts', () => {
|
||||
cy.get(getTestSelector('nft-bag')).should('exist')
|
||||
})
|
||||
|
||||
it('should go view my nfts', () => {
|
||||
xit('should go view my nfts', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('nft-view-self-nfts')).click()
|
||||
cy.get(getTestSelector('nft-explore-nfts-button')).should('exist')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => cy.visit('/pool'))
|
||||
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
xit('add liquidity links to /add/ETH', () => {
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
|
||||
@@ -138,8 +138,8 @@
|
||||
"@types/react-relay": "^13.0.2",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.2.0",
|
||||
"@uniswap/analytics-events": "1.3.1",
|
||||
"@uniswap/conedison": "^1.1.0",
|
||||
"@uniswap/conedison": "^1.1.1",
|
||||
"@uniswap/analytics-events": "^1.4.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
|
||||
21
src/assets/svg/fiat_mask.svg
Normal file
21
src/assets/svg/fiat_mask.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 3.5 MiB |
13
src/components/Button/LoadingButtonSpinner.tsx
Normal file
13
src/components/Button/LoadingButtonSpinner.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { SpinnerSVG } from 'theme'
|
||||
|
||||
const ButtonLoadingSpinner = (props: React.ComponentPropsWithoutRef<'svg'>) => (
|
||||
<SpinnerSVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M18.8334 10.0003C18.8334 14.6027 15.1025 18.3337 10.5001 18.3337C5.89771 18.3337 2.16675 14.6027 2.16675 10.0003C2.16675 5.39795 5.89771 1.66699 10.5001 1.66699C15.1025 1.66699 18.8334 5.39795 18.8334 10.0003ZM4.66675 10.0003C4.66675 13.222 7.27842 15.8337 10.5001 15.8337C13.7217 15.8337 16.3334 13.222 16.3334 10.0003C16.3334 6.77867 13.7217 4.16699 10.5001 4.16699C7.27842 4.16699 4.66675 6.77867 4.66675 10.0003Z"
|
||||
/>
|
||||
<path d="M17.5834 10.0003C18.2738 10.0003 18.843 9.4376 18.7398 8.755C18.6392 8.0891 18.458 7.43633 18.1991 6.8113C17.7803 5.80025 17.1665 4.88159 16.3926 4.10777C15.6188 3.33395 14.7002 2.72012 13.6891 2.30133C13.0641 2.04243 12.4113 1.86121 11.7454 1.76057C11.0628 1.6574 10.5001 2.22664 10.5001 2.91699C10.5001 3.60735 11.066 4.15361 11.7405 4.30041C12.0789 4.37406 12.4109 4.47786 12.7324 4.61103C13.4401 4.90418 14.0832 5.33386 14.6249 5.87554C15.1665 6.41721 15.5962 7.06027 15.8894 7.76801C16.0225 8.08949 16.1264 8.42147 16.2 8.75986C16.3468 9.43443 16.8931 10.0003 17.5834 10.0003Z" />
|
||||
</SpinnerSVG>
|
||||
)
|
||||
|
||||
export default ButtonLoadingSpinner
|
||||
@@ -5,16 +5,31 @@ import styled, { DefaultTheme, useTheme } from 'styled-components/macro'
|
||||
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
export { default as LoadingButtonSpinner } from './LoadingButtonSpinner'
|
||||
|
||||
type ButtonProps = Omit<ButtonPropsOriginal, 'css'>
|
||||
|
||||
export const BaseButton = styled(RebassButton)<
|
||||
{
|
||||
padding?: string
|
||||
width?: string
|
||||
$borderRadius?: string
|
||||
altDisabledStyle?: boolean
|
||||
} & ButtonProps
|
||||
>`
|
||||
const ButtonOverlay = styled.div`
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
border-radius: inherit;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: 150ms ease background-color;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
type BaseButtonProps = {
|
||||
padding?: string
|
||||
width?: string
|
||||
$borderRadius?: string
|
||||
altDisabledStyle?: boolean
|
||||
} & ButtonProps
|
||||
|
||||
export const BaseButton = styled(RebassButton)<BaseButtonProps>`
|
||||
padding: ${({ padding }) => padding ?? '16px'};
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
font-weight: 500;
|
||||
@@ -86,7 +101,7 @@ export const SmallButtonPrimary = styled(ButtonPrimary)`
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
export const ButtonLight = styled(BaseButton)`
|
||||
const BaseButtonLight = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 20px;
|
||||
@@ -103,6 +118,19 @@ export const ButtonLight = styled(BaseButton)`
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
|
||||
:hover {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
}
|
||||
|
||||
:active {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
}
|
||||
|
||||
:disabled {
|
||||
opacity: 0.4;
|
||||
:hover {
|
||||
@@ -369,18 +397,6 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
|
||||
}
|
||||
}
|
||||
|
||||
const ButtonOverlay = styled.div`
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: 150ms ease background-color;
|
||||
width: 100%;
|
||||
`
|
||||
export enum ButtonSize {
|
||||
small,
|
||||
medium,
|
||||
@@ -395,7 +411,7 @@ export enum ButtonEmphasis {
|
||||
warning,
|
||||
destructive,
|
||||
}
|
||||
interface BaseButtonProps {
|
||||
interface BaseThemeButtonProps {
|
||||
size: ButtonSize
|
||||
emphasis: ButtonEmphasis
|
||||
}
|
||||
@@ -474,7 +490,7 @@ function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; em
|
||||
}
|
||||
}
|
||||
|
||||
const BaseThemeButton = styled.button<BaseButtonProps>`
|
||||
const BaseThemeButton = styled.button<BaseThemeButtonProps>`
|
||||
align-items: center;
|
||||
background-color: ${pickThemeButtonBackgroundColor};
|
||||
border-radius: 16px;
|
||||
@@ -491,16 +507,13 @@ const BaseThemeButton = styled.button<BaseButtonProps>`
|
||||
padding: ${pickThemeButtonPadding};
|
||||
position: relative;
|
||||
transition: 150ms ease opacity;
|
||||
user-select: none;
|
||||
|
||||
:active {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
}
|
||||
:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
:focus {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
@@ -511,9 +524,20 @@ const BaseThemeButton = styled.button<BaseButtonProps>`
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
}
|
||||
:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
:disabled:active,
|
||||
:disabled:focus,
|
||||
:disabled:hover {
|
||||
${ButtonOverlay} {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseButtonProps {}
|
||||
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseThemeButtonProps {}
|
||||
|
||||
export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
|
||||
return (
|
||||
@@ -523,3 +547,12 @@ export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
|
||||
</BaseThemeButton>
|
||||
)
|
||||
}
|
||||
|
||||
export const ButtonLight = ({ children, ...rest }: BaseButtonProps) => {
|
||||
return (
|
||||
<BaseButtonLight {...rest}>
|
||||
<ButtonOverlay />
|
||||
{children}
|
||||
</BaseButtonLight>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import { LandingRedirectVariant, useLandingRedirectFlag } from 'featureFlags/flags/landingRedirect'
|
||||
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
@@ -208,6 +210,18 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.permit2}
|
||||
label="Permit 2 / Universal Router"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={LandingRedirectVariant}
|
||||
value={useLandingRedirectFlag()}
|
||||
featureFlag={FeatureFlag.landingRedirect}
|
||||
label="Landing Page Redirect"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useFiatOnrampFlag()}
|
||||
featureFlag={FeatureFlag.fiatOnramp}
|
||||
label="Fiat on-ramp"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
149
src/components/FiatOnrampAnnouncement/index.tsx
Normal file
149
src/components/FiatOnrampAnnouncement/index.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import fiatMaskUrl from 'assets/svg/fiat_mask.svg'
|
||||
import { BaseVariant } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useToggleWalletDropdown } from 'state/application/hooks'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useFiatOnrampAck } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const Arrow = styled.div`
|
||||
top: -4px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
width: 16px;
|
||||
|
||||
::before {
|
||||
background: hsl(315.75, 93%, 83%);
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
width: 16px;
|
||||
}
|
||||
`
|
||||
const ArrowWrapper = styled.div`
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 90%;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
min-height: 92px;
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
|
||||
right: 36px;
|
||||
}
|
||||
`
|
||||
|
||||
const CloseIcon = styled(X)`
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
`
|
||||
const Wrapper = styled.button`
|
||||
background: radial-gradient(105% 250% at 100% 5%, hsla(318, 95%, 85%) 1%, hsla(331, 80%, 75%, 0.1) 84%),
|
||||
linear-gradient(180deg, hsla(296, 92%, 67%, 0.5) 0%, hsla(313, 96%, 60%, 0.5) 130%);
|
||||
background-color: hsla(297, 93%, 68%, 1);
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: start;
|
||||
max-width: 320px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${fiatMaskUrl});
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: -154px; // roughly width of fiat mask image
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
const Header = styled(ThemedText.SubHeader)`
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 12px 12px 4px;
|
||||
position: relative;
|
||||
`
|
||||
const Body = styled(ThemedText.BodySmall)`
|
||||
color: white;
|
||||
margin: 0 12px 12px 12px !important;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const ANNOUNCEMENT_RENDERED = 'FiatOnrampAnnouncement-rendered'
|
||||
const ANNOUNCEMENT_DISMISSED = 'FiatOnrampAnnouncement-dismissed'
|
||||
|
||||
const MAX_RENDER_COUNT = 3
|
||||
export function FiatOnrampAnnouncement() {
|
||||
const { account } = useWeb3React()
|
||||
const [acks, acknowledge] = useFiatOnrampAck()
|
||||
const [locallyDismissed, setLocallyDismissed] = useState(false)
|
||||
useEffect(() => {
|
||||
if (!sessionStorage.getItem(ANNOUNCEMENT_RENDERED)) {
|
||||
acknowledge({ renderCount: acks?.renderCount + 1 })
|
||||
sessionStorage.setItem(ANNOUNCEMENT_RENDERED, 'true')
|
||||
}
|
||||
}, [acknowledge, acks])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setLocallyDismissed(true)
|
||||
sessionStorage.setItem(ANNOUNCEMENT_DISMISSED, 'true')
|
||||
}, [])
|
||||
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const handleClick = useCallback(() => {
|
||||
toggleWalletDropdown()
|
||||
acknowledge({ user: true })
|
||||
}, [acknowledge, toggleWalletDropdown])
|
||||
|
||||
const fiatOnrampFlag = useFiatOnrampFlag()
|
||||
const openModal = useAppSelector((state) => state.application.openModal)
|
||||
|
||||
if (
|
||||
!account ||
|
||||
acks?.user ||
|
||||
fiatOnrampFlag === BaseVariant.Control ||
|
||||
locallyDismissed ||
|
||||
sessionStorage.getItem(ANNOUNCEMENT_DISMISSED) ||
|
||||
acks?.renderCount >= MAX_RENDER_COUNT ||
|
||||
isMobile ||
|
||||
openModal !== null
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ArrowWrapper>
|
||||
<Arrow />
|
||||
<CloseIcon onClick={handleClose} />
|
||||
<Wrapper onClick={handleClick}>
|
||||
<Header>
|
||||
<Trans>Buy crypto</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>Get tokens at the best prices in web3 on Uniswap, powered by Moonpay.</Trans>
|
||||
</Body>
|
||||
</Wrapper>
|
||||
</ArrowWrapper>
|
||||
)
|
||||
}
|
||||
143
src/components/FiatOnrampModal/index.tsx
Normal file
143
src/components/FiatOnrampModal/index.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CustomLightSpinner, ThemedText } from 'theme'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import Modal from '../Modal'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 20px;
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
margin: 0;
|
||||
min-height: 720px;
|
||||
min-width: 375px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const ErrorText = styled(ThemedText.BodyPrimary)`
|
||||
color: ${({ theme }) => theme.accentFailure};
|
||||
margin: auto !important;
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
`
|
||||
const StyledIframe = styled.iframe`
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
border-radius: 12px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: calc(100% - 16px);
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: calc(100% - 16px);
|
||||
`
|
||||
const StyledSpinner = styled(CustomLightSpinner)`
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
`
|
||||
|
||||
const MOONPAY_SUPPORTED_CURRENCY_CODES = [
|
||||
'eth',
|
||||
'eth_arbitrum',
|
||||
'eth_optimism',
|
||||
'eth_polygon',
|
||||
'weth',
|
||||
'wbtc',
|
||||
'matic_polygon',
|
||||
'polygon',
|
||||
'usdc_arbitrum',
|
||||
'usdc_optimism',
|
||||
'usdc_polygon',
|
||||
]
|
||||
|
||||
export default function FiatOnrampModal() {
|
||||
const { account } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
const closeModal = useCloseModal(ApplicationModal.FIAT_ONRAMP)
|
||||
const fiatOnrampModalOpen = useModalIsOpen(ApplicationModal.FIAT_ONRAMP)
|
||||
|
||||
const [signedIframeUrl, setSignedIframeUrl] = useState<string | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const fetchSignedIframeUrl = useCallback(async () => {
|
||||
if (!account) {
|
||||
setError('Please connect an account before making a purchase.')
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const signedIframeUrlFetchEndpoint = process.env.REACT_APP_MOONPAY_LINK as string
|
||||
const res = await fetch(signedIframeUrlFetchEndpoint, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
colorCode: theme.accentAction,
|
||||
defaultCurrencyCode: 'eth',
|
||||
redirectUrl: 'https://app.uniswap.org/#/swap',
|
||||
walletAddresses: JSON.stringify(
|
||||
MOONPAY_SUPPORTED_CURRENCY_CODES.reduce(
|
||||
(acc, currencyCode) => ({
|
||||
...acc,
|
||||
[currencyCode]: account,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
),
|
||||
}),
|
||||
})
|
||||
const { url } = await res.json()
|
||||
setSignedIframeUrl(url)
|
||||
} catch (e) {
|
||||
console.log('there was an error fetching the link', e)
|
||||
setError(e.toString())
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [account, theme.accentAction])
|
||||
|
||||
useEffect(() => {
|
||||
fetchSignedIframeUrl()
|
||||
}, [fetchSignedIframeUrl])
|
||||
|
||||
return (
|
||||
<Modal isOpen={fiatOnrampModalOpen} onDismiss={closeModal} maxHeight={720}>
|
||||
<Wrapper data-testid="fiat-onramp-modal">
|
||||
{error ? (
|
||||
<>
|
||||
<ThemedText.MediumHeader>
|
||||
<Trans>Moonpay Fiat On-ramp iframe</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<ErrorText>
|
||||
<Trans>something went wrong!</Trans>
|
||||
<br />
|
||||
{error}
|
||||
</ErrorText>
|
||||
</>
|
||||
) : loading ? (
|
||||
<StyledSpinner src={Circle} alt="loading spinner" size="90px" />
|
||||
) : (
|
||||
<StyledIframe src={signedIframeUrl ?? ''} frameBorder="0" title="fiat-onramp-iframe" />
|
||||
)}
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { isMobile } from '../../utils/userAgent'
|
||||
|
||||
const AnimatedDialogOverlay = animated(DialogOverlay)
|
||||
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: boolean }>`
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ $scrollOverlay?: boolean }>`
|
||||
&[data-reach-dialog-overlay] {
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
background-color: transparent;
|
||||
@@ -17,7 +17,7 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: bool
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-y: ${({ scrollOverlay }) => scrollOverlay && 'scroll'};
|
||||
overflow-y: ${({ $scrollOverlay }) => $scrollOverlay && 'scroll'};
|
||||
justify-content: center;
|
||||
|
||||
background-color: ${({ theme }) => theme.backgroundScrim};
|
||||
@@ -89,7 +89,7 @@ interface ModalProps {
|
||||
maxWidth?: number
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
scrollOverlay?: boolean
|
||||
$scrollOverlay?: boolean
|
||||
hideBorder?: boolean
|
||||
isBottomSheet?: boolean
|
||||
}
|
||||
@@ -103,7 +103,7 @@ export default function Modal({
|
||||
initialFocusRef,
|
||||
children,
|
||||
onSwipe = onDismiss,
|
||||
scrollOverlay,
|
||||
$scrollOverlay,
|
||||
isBottomSheet = isMobile,
|
||||
hideBorder = false,
|
||||
}: ModalProps) {
|
||||
@@ -136,7 +136,7 @@ export default function Modal({
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
unstable_lockFocusAcrossFrames={false}
|
||||
scrollOverlay={scrollOverlay}
|
||||
$scrollOverlay={$scrollOverlay}
|
||||
>
|
||||
<StyledDialogContent
|
||||
{...(isMobile
|
||||
@@ -149,7 +149,7 @@ export default function Modal({
|
||||
$minHeight={minHeight}
|
||||
$maxHeight={maxHeight}
|
||||
$isBottomSheet={isBottomSheet}
|
||||
$scrollOverlay={scrollOverlay}
|
||||
$scrollOverlay={$scrollOverlay}
|
||||
$hideBorder={hideBorder}
|
||||
$maxWidth={maxWidth}
|
||||
>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { body, bodySmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { ReactNode, useReducer, useRef } from 'react'
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
|
||||
import { useToggleModal } from '../../state/application/hooks'
|
||||
@@ -50,8 +51,13 @@ const PrimaryMenuRow = ({
|
||||
)
|
||||
}
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
const PrimaryMenuRowText = ({ children }: { children: ReactNode }) => {
|
||||
return <Box className={`${styles.PrimaryText} ${body}`}>{children}</Box>
|
||||
return <StyledBox className={`${styles.PrimaryText} ${body}`}>{children}</StyledBox>
|
||||
}
|
||||
|
||||
PrimaryMenuRow.Text = PrimaryMenuRowText
|
||||
@@ -115,7 +121,6 @@ export const MenuDropdown = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ const baseNavDropdown = style([
|
||||
borderWidth: '1px',
|
||||
paddingBottom: '8',
|
||||
paddingTop: '8',
|
||||
zIndex: '2',
|
||||
}),
|
||||
{
|
||||
boxShadow: '0px 4px 12px 0px #00000026',
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { Box, BoxProps } from 'nft/components/Box'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { ForwardedRef, forwardRef } from 'react'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import * as styles from './NavDropdown.css'
|
||||
|
||||
export const NavDropdown = forwardRef((props: BoxProps, ref: ForwardedRef<HTMLElement>) => {
|
||||
const isMobile = useIsMobile()
|
||||
return <Box ref={ref} className={isMobile ? styles.mobileNavDropdown : styles.NavDropdown} {...props} />
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
style={{ zIndex: Z_INDEX.modal }}
|
||||
className={isMobile ? styles.mobileNavDropdown : styles.NavDropdown}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
NavDropdown.displayName = 'NavDropdown'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { LandingRedirectVariant, useLandingRedirectFlag } from 'featureFlags/flags/landingRedirect'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
@@ -80,6 +81,7 @@ export const PageTabs = () => {
|
||||
const Navbar = () => {
|
||||
const isNftPage = useIsNftPage()
|
||||
const navigate = useNavigate()
|
||||
const landingRedirectFlag = useLandingRedirectFlag()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -92,7 +94,10 @@ const Navbar = () => {
|
||||
height="48"
|
||||
className={styles.logo}
|
||||
onClick={() => {
|
||||
navigate('/')
|
||||
navigate({
|
||||
pathname: '/',
|
||||
search: landingRedirectFlag === LandingRedirectVariant.Enabled ? '?intro=true' : undefined,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
margin-right: 8px;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const Grouping = styled(AutoColumn)`
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
/* background-color: ${({ theme }) => theme.backgroundInteractive}; */
|
||||
border-radius: 16px;
|
||||
`
|
||||
|
||||
const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>`
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: ${({ theme, confirmed, disabled }) =>
|
||||
disabled ? theme.deprecated_bg3 : confirmed ? theme.accentSuccess : theme.accentAction};
|
||||
border-radius: 50%;
|
||||
color: ${({ theme, disabled }) => (disabled ? theme.textTertiary : theme.textPrimary)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 8px;
|
||||
font-size: 16px;
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
const CircleRow = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
interface ProgressCirclesProps {
|
||||
steps: boolean[]
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on array of steps, create a step counter of circles.
|
||||
* A circle can be enabled, disabled, or confirmed. States are derived
|
||||
* from previous step.
|
||||
*
|
||||
* An extra circle is added to represent the ability to swap, add, or remove.
|
||||
* This step will never be marked as complete (because no 'txn done' state in body ui).
|
||||
*
|
||||
* @param steps array of booleans where true means step is complete
|
||||
*/
|
||||
export default function ProgressCircles({ steps, disabled = false, ...rest }: ProgressCirclesProps) {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Wrapper justify="center" {...rest}>
|
||||
<Grouping>
|
||||
{steps.map((step, i) => {
|
||||
return (
|
||||
<CircleRow key={i}>
|
||||
<Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}>
|
||||
{step ? '✓' : i + 1 + '.'}
|
||||
</Circle>
|
||||
<ThemedText.DeprecatedMain color={theme.deprecated_text4}>|</ThemedText.DeprecatedMain>
|
||||
</CircleRow>
|
||||
)
|
||||
})}
|
||||
<Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1 + '.'}</Circle>
|
||||
</Grouping>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -4,4 +4,4 @@ export const LARGE_MEDIA_BREAKPOINT = '840px'
|
||||
export const MEDIUM_MEDIA_BREAKPOINT = '720px'
|
||||
export const SMALL_MEDIA_BREAKPOINT = '540px'
|
||||
export const MOBILE_MEDIA_BREAKPOINT = '420px'
|
||||
export const SMALL_MOBILE_MEDIA_BREAKPOINT = '390px'
|
||||
// export const SMALL_MOBILE_MEDIA_BREAKPOINT = '390px'
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import FiatOnrampModal from 'components/FiatOnrampModal'
|
||||
import { BaseVariant } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import NftExploreBanner from 'nft/components/nftExploreBanner/NftExploreBanner'
|
||||
import { lazy } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
@@ -17,21 +18,18 @@ export default function TopLevelModals() {
|
||||
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const blockedAccountModalOpen = useModalIsOpen(ApplicationModal.BLOCKED_ACCOUNT)
|
||||
const { account } = useWeb3React()
|
||||
const location = useLocation()
|
||||
const pageShowsNftPromoBanner =
|
||||
location.pathname.startsWith('/swap') ||
|
||||
location.pathname.startsWith('/tokens') ||
|
||||
location.pathname.startsWith('/pool')
|
||||
useAccountRiskCheck(account)
|
||||
const open = Boolean(blockedAccountModalOpen && account)
|
||||
const accountBlocked = Boolean(blockedAccountModalOpen && account)
|
||||
const fiatOnrampFlagEnabled = useFiatOnrampFlag() === BaseVariant.Enabled
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
|
||||
<ConnectedAccountBlocked account={account} isOpen={open} />
|
||||
<ConnectedAccountBlocked account={account} isOpen={accountBlocked} />
|
||||
<Bag />
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
{pageShowsNftPromoBanner && <NftExploreBanner />}
|
||||
{fiatOnrampFlagEnabled && <FiatOnrampModal />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ export default function TransactionConfirmationModal({
|
||||
|
||||
// confirmation screen
|
||||
return (
|
||||
<Modal isOpen={isOpen} scrollOverlay={true} onDismiss={onDismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} $scrollOverlay={true} onDismiss={onDismiss} maxHeight={90}>
|
||||
{isL2ChainId(chainId) && (hash || attemptingTxn) ? (
|
||||
<L2Content chainId={chainId} hash={hash} onDismiss={onDismiss} pendingText={pendingText} />
|
||||
) : attemptingTxn ? (
|
||||
|
||||
@@ -1,33 +1,69 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatUSDPrice } from '@uniswap/conedison/format'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ButtonEmphasis, ButtonSize, LoadingButtonSpinner, ThemeButton } from 'components/Button'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { BaseVariant } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import ms from 'ms.macro'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Copy, ExternalLink, Power } from 'react-feather'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Copy, CreditCard, ExternalLink as ExternalLinkIcon, Info, Power } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useCurrencyBalanceString } from 'state/connection/hooks'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { useFiatOnrampAck } from 'state/user/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import styled, { css, keyframes } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
import { shortenAddress } from '../../nft/utils/address'
|
||||
import { useCloseModal, useToggleModal } from '../../state/application/hooks'
|
||||
import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import IconButton, { IconHoverText } from './IconButton'
|
||||
|
||||
const BuyCryptoButtonBorderKeyframes = keyframes`
|
||||
0% {
|
||||
border-color: transparent;
|
||||
}
|
||||
33% {
|
||||
border-color: hsla(225, 95%, 63%, 1);
|
||||
}
|
||||
66% {
|
||||
border-color: hsla(267, 95%, 63%, 1);
|
||||
}
|
||||
100% {
|
||||
border-color: transparent;
|
||||
}
|
||||
`
|
||||
|
||||
const BuyCryptoButton = styled(ThemeButton)<{ $animateBorder: boolean }>`
|
||||
border-color: transparent;
|
||||
border-radius: 12px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
height: 40px;
|
||||
margin-top: 12px;
|
||||
animation-direction: alternate;
|
||||
animation-duration: ${({ theme }) => theme.transition.duration.slow};
|
||||
animation-fill-mode: none;
|
||||
animation-iteration-count: 2;
|
||||
animation-name: ${BuyCryptoButtonBorderKeyframes};
|
||||
animation-play-state: ${({ $animateBorder }) => ($animateBorder ? 'running' : 'paused')};
|
||||
animation-timing-function: ${({ theme }) => theme.transition.timing.inOut};
|
||||
`
|
||||
const WalletButton = styled(ThemeButton)`
|
||||
border-radius: 12px;
|
||||
padding-top: 10px;
|
||||
@@ -75,7 +111,20 @@ const USDText = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
||||
const FiatOnrampNotAvailableText = styled(ThemedText.Caption)`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
const FiatOnrampAvailabilityExternalLink = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 14px;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
width: 14px;
|
||||
`
|
||||
const FlexContainer = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
@@ -108,7 +157,14 @@ const AccountContainer = styled(ThemedText.BodySmall)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
margin-top: 2.5px;
|
||||
`
|
||||
|
||||
const StyledInfoIcon = styled(Info)`
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
flex: 1 1 auto;
|
||||
`
|
||||
const StyledLoadingButtonSpinner = styled(LoadingButtonSpinner)`
|
||||
fill: ${({ theme }) => theme.accentAction};
|
||||
`
|
||||
const BalanceWrapper = styled.div`
|
||||
padding: 16px 0;
|
||||
`
|
||||
@@ -137,7 +193,6 @@ const AuthenticatedHeader = () => {
|
||||
} = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
|
||||
const navigate = useNavigate()
|
||||
const closeModal = useCloseModal(ApplicationModal.WALLET_DROPDOWN)
|
||||
|
||||
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
|
||||
const resetSellAssets = useSellAsset((state) => state.reset)
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
@@ -147,7 +202,7 @@ const AuthenticatedHeader = () => {
|
||||
const isUnclaimed = useUserHasAvailableClaim(account)
|
||||
const connectionType = getConnection(connector).type
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
const nativeCurrencyPrice = useStablecoinPrice(nativeCurrency ?? undefined) || 0
|
||||
const nativeCurrencyPrice = useStablecoinPrice(nativeCurrency ?? undefined)
|
||||
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const openNftModal = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const disconnect = useCallback(() => {
|
||||
@@ -159,18 +214,63 @@ const AuthenticatedHeader = () => {
|
||||
}, [connector, dispatch])
|
||||
|
||||
const amountUSD = useMemo(() => {
|
||||
if (!nativeCurrencyPrice || !balanceString) return undefined
|
||||
const price = parseFloat(nativeCurrencyPrice.toFixed(5))
|
||||
const balance = parseFloat(balanceString || '0')
|
||||
const balance = parseFloat(balanceString)
|
||||
return price * balance
|
||||
}, [balanceString, nativeCurrencyPrice])
|
||||
|
||||
const navigateToProfile = () => {
|
||||
const navigateToProfile = useCallback(() => {
|
||||
resetSellAssets()
|
||||
setSellPageState(ProfilePageStateType.VIEWING)
|
||||
clearCollectionFilters()
|
||||
navigate('/nfts/profile')
|
||||
closeModal()
|
||||
}
|
||||
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState])
|
||||
|
||||
const fiatOnrampFlag = useFiatOnrampFlag()
|
||||
// animate the border of the buy crypto button when a user navigates here from the feature announcement
|
||||
// can be removed when components/FiatOnrampAnnouncment.tsx is no longer used
|
||||
const [acknowledgements, acknowledge] = useFiatOnrampAck()
|
||||
const animateBuyCryptoButtonBorder = acknowledgements?.user && !acknowledgements.system
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
let timeoutId = 0
|
||||
if (animateBuyCryptoButtonBorder) {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (stale) return
|
||||
acknowledge({ system: true })
|
||||
}, ms`2 seconds`) as unknown as number
|
||||
// as unknown as number is necessary so it's not incorrectly typed as a NodeJS.Timeout
|
||||
}
|
||||
return () => {
|
||||
stale = true
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}, [acknowledge, animateBuyCryptoButtonBorder])
|
||||
|
||||
const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
|
||||
const [shouldCheck, setShouldCheck] = useState(false)
|
||||
const {
|
||||
available: fiatOnrampAvailable,
|
||||
availabilityChecked: fiatOnrampAvailabilityChecked,
|
||||
error,
|
||||
loading: fiatOnrampAvailabilityLoading,
|
||||
} = useFiatOnrampAvailability(shouldCheck, openFiatOnrampModal)
|
||||
|
||||
const handleBuyCryptoClick = useCallback(() => {
|
||||
if (!fiatOnrampAvailabilityChecked) {
|
||||
setShouldCheck(true)
|
||||
} else if (fiatOnrampAvailable) {
|
||||
openFiatOnrampModal()
|
||||
}
|
||||
}, [fiatOnrampAvailabilityChecked, fiatOnrampAvailable, openFiatOnrampModal])
|
||||
const disableBuyCryptoButton = Boolean(
|
||||
error || (!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) || fiatOnrampAvailabilityLoading
|
||||
)
|
||||
const [showFiatOnrampUnavailableTooltip, setShow] = useState<boolean>(false)
|
||||
const openFiatOnrampUnavailableTooltip = useCallback(() => setShow(true), [setShow])
|
||||
const closeFiatOnrampUnavailableTooltip = useCallback(() => setShow(false), [setShow])
|
||||
|
||||
return (
|
||||
<AuthenticatedHeaderWrapper>
|
||||
@@ -192,7 +292,7 @@ const AuthenticatedHeader = () => {
|
||||
<IconButton onClick={copy} Icon={Copy}>
|
||||
{isCopied ? <Trans>Copied!</Trans> : <Trans>Copy</Trans>}
|
||||
</IconButton>
|
||||
<IconButton href={`${explorer}address/${account}`} target="_blank" Icon={ExternalLink}>
|
||||
<IconButton href={`${explorer}address/${account}`} target="_blank" Icon={ExternalLinkIcon}>
|
||||
<Trans>Explore</Trans>
|
||||
</IconButton>
|
||||
<IconButton data-testid="wallet-disconnect" onClick={disconnect} Icon={Power}>
|
||||
@@ -205,7 +305,7 @@ const AuthenticatedHeader = () => {
|
||||
<Text fontSize={36} fontWeight={400}>
|
||||
{balanceString} {nativeCurrencySymbol}
|
||||
</Text>
|
||||
<USDText>${amountUSD.toFixed(2)} USD</USDText>
|
||||
{amountUSD !== undefined && <USDText>{formatUSDPrice(amountUSD)} USD</USDText>}
|
||||
</BalanceWrapper>
|
||||
<ProfileButton
|
||||
data-testid="nft-view-self-nfts"
|
||||
@@ -215,6 +315,44 @@ const AuthenticatedHeader = () => {
|
||||
>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</ProfileButton>
|
||||
{fiatOnrampFlag === BaseVariant.Enabled && (
|
||||
<>
|
||||
<BuyCryptoButton
|
||||
$animateBorder={animateBuyCryptoButtonBorder}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
onClick={handleBuyCryptoClick}
|
||||
disabled={disableBuyCryptoButton}
|
||||
>
|
||||
{error ? (
|
||||
<ThemedText.BodyPrimary>{error}</ThemedText.BodyPrimary>
|
||||
) : (
|
||||
<>
|
||||
{fiatOnrampAvailabilityLoading ? <StyledLoadingButtonSpinner /> : <CreditCard />}{' '}
|
||||
<Trans>Buy crypto</Trans>
|
||||
</>
|
||||
)}
|
||||
</BuyCryptoButton>
|
||||
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
|
||||
<FiatOnrampNotAvailableText marginTop="8px">
|
||||
<Trans>Not available in your region</Trans>
|
||||
<Tooltip
|
||||
show={showFiatOnrampUnavailableTooltip}
|
||||
text={<Trans>Moonpay is not available in some regions. Click to learn more.</Trans>}
|
||||
>
|
||||
<FiatOnrampAvailabilityExternalLink
|
||||
onMouseEnter={openFiatOnrampUnavailableTooltip}
|
||||
onMouseLeave={closeFiatOnrampUnavailableTooltip}
|
||||
style={{ color: 'inherit' }}
|
||||
href="https://support.uniswap.org/hc/en-us/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-"
|
||||
>
|
||||
<StyledInfoIcon />
|
||||
</FiatOnrampAvailabilityExternalLink>
|
||||
</Tooltip>
|
||||
</FiatOnrampNotAvailableText>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isUnclaimed && (
|
||||
<UNIButton onClick={openClaimModal} size={ButtonSize.medium} emphasis={ButtonEmphasis.medium}>
|
||||
<Trans>Claim</Trans> {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} <Trans>reward</Trans>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { FiatOnrampAnnouncement } from 'components/FiatOnrampAnnouncement'
|
||||
import { IconWrapper } from 'components/Identicon/StatusIcon'
|
||||
import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getConnection } from 'connection/utils'
|
||||
@@ -9,7 +10,7 @@ import { Portal } from 'nft/components/common/Portal'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { getIsValidSwapQuote } from 'pages/Swap'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useDerivedSwapInfo } from 'state/swap/hooks'
|
||||
@@ -205,6 +206,10 @@ function Web3StatusInner() {
|
||||
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
|
||||
const theme = useTheme()
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const handleWalletDropdownClick = useCallback(() => {
|
||||
sendAnalyticsEvent('FOR Account Dropdown Button Clicks')
|
||||
toggleWalletDropdown()
|
||||
}, [toggleWalletDropdown])
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const walletIsOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
@@ -221,13 +226,12 @@ function Web3StatusInner() {
|
||||
const pending = sortedRecentTransactions.filter((tx) => !tx.receipt).map((tx) => tx.hash)
|
||||
|
||||
const hasPendingTransactions = !!pending.length
|
||||
const toggleWallet = toggleWalletDropdown
|
||||
|
||||
if (!chainId) {
|
||||
return null
|
||||
} else if (error) {
|
||||
return (
|
||||
<Web3StatusError onClick={toggleWallet}>
|
||||
<Web3StatusError onClick={handleWalletDropdownClick}>
|
||||
<NetworkIcon />
|
||||
<Text>
|
||||
<Trans>Error</Trans>
|
||||
@@ -243,7 +247,7 @@ function Web3StatusInner() {
|
||||
return (
|
||||
<Web3StatusConnected
|
||||
data-testid="web3-status-connected"
|
||||
onClick={toggleWallet}
|
||||
onClick={handleWalletDropdownClick}
|
||||
pending={hasPendingTransactions}
|
||||
isClaimAvailable={isClaimAvailable}
|
||||
>
|
||||
@@ -281,7 +285,7 @@ function Web3StatusInner() {
|
||||
<Trans>Connect</Trans>
|
||||
</StyledConnectButton>
|
||||
<VerticalDivider />
|
||||
<ChevronWrapper onClick={toggleWalletDropdown} data-testid="navbar-toggle-dropdown">
|
||||
<ChevronWrapper onClick={handleWalletDropdownClick} data-testid="navbar-toggle-dropdown">
|
||||
{walletIsOpen ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
|
||||
</ChevronWrapper>
|
||||
</Web3StatusConnectWrapper>
|
||||
@@ -312,6 +316,7 @@ export default function Web3Status() {
|
||||
return (
|
||||
<span ref={ref}>
|
||||
<Web3StatusInner />
|
||||
<FiatOnrampAnnouncement />
|
||||
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
|
||||
<Portal>
|
||||
<span ref={walletRef}>
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import { LoadingView, SubmittedView } from '../ModalViews'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
|
||||
|
||||
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
interface StakingModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
stakingInfo: StakingInfo
|
||||
}
|
||||
|
||||
export default function ClaimRewardModal({ isOpen, onDismiss, stakingInfo }: StakingModalProps) {
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const addTransaction = useTransactionAdder()
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState(false)
|
||||
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
|
||||
|
||||
async function onClaimReward() {
|
||||
if (stakingContract && stakingInfo?.stakedAmount && account) {
|
||||
setAttempting(true)
|
||||
await stakingContract
|
||||
.getReward({ gasLimit: 350000 })
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
type: TransactionType.CLAIM,
|
||||
recipient: account,
|
||||
})
|
||||
setHash(response.hash)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let error: ReactNode | undefined
|
||||
if (!account) {
|
||||
error = <Trans>Connect Wallet</Trans>
|
||||
}
|
||||
if (!stakingInfo?.stakedAmount) {
|
||||
error = error ?? <Trans>Enter an amount</Trans>
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Claim</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<CloseIcon onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
{stakingInfo?.earnedAmount && (
|
||||
<AutoColumn justify="center" gap="md">
|
||||
<ThemedText.HeadlineLarge>{stakingInfo?.earnedAmount?.toSignificant(6)}</ThemedText.HeadlineLarge>
|
||||
<ThemedText.DeprecatedBody>
|
||||
<Trans>Unclaimed UNI</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
)}
|
||||
<ThemedText.DeprecatedSubHeader style={{ textAlign: 'center' }}>
|
||||
<Trans>When you claim without withdrawing your liquidity remains in the mining pool.</Trans>
|
||||
</ThemedText.DeprecatedSubHeader>
|
||||
<ButtonError disabled={!!error} error={!!error && !!stakingInfo?.stakedAmount} onClick={onClaimReward}>
|
||||
{error ?? <Trans>Claim</Trans>}
|
||||
</ButtonError>
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Claiming {stakingInfo?.earnedAmount?.toSignificant(6)} UNI</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</LoadingView>
|
||||
)}
|
||||
{hash && (
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<ThemedText.DeprecatedLargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Claimed UNI!</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</SubmittedView>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import JSBI from 'jsbi'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { BIG_INT_SECONDS_IN_WEEK } from '../../constants/misc'
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
import useStablecoinPrice from '../../hooks/useStablecoinPrice'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useV2Pair } from '../../hooks/useV2Pairs'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { StyledInternalLink, ThemedText } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowBetween } from '../Row'
|
||||
import { Break, CardBGImage, CardNoise } from './styled'
|
||||
|
||||
const StatContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const Wrapper = styled(AutoColumn)<{ showBackground: boolean; bgColor: any }>`
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
opacity: ${({ showBackground }) => (showBackground ? '1' : '1')};
|
||||
background: ${({ theme, bgColor, showBackground }) =>
|
||||
`radial-gradient(91.85% 100% at 1.84% 0%, ${bgColor} 0%, ${
|
||||
showBackground ? theme.black : theme.deprecated_bg5
|
||||
} 100%) `};
|
||||
color: ${({ theme, showBackground }) => (showBackground ? theme.white : theme.textPrimary)} !important;
|
||||
|
||||
${({ showBackground }) =>
|
||||
showBackground &&
|
||||
` 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);`}
|
||||
`
|
||||
|
||||
const TopSection = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr 120px;
|
||||
grid-gap: 0px;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
grid-template-columns: 48px 1fr 96px;
|
||||
`};
|
||||
`
|
||||
|
||||
const BottomSection = styled.div<{ showBackground: boolean }>`
|
||||
padding: 12px 16px;
|
||||
opacity: ${({ showBackground }) => (showBackground ? '1' : '0.4')};
|
||||
border-radius: 0 0 12px 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo }) {
|
||||
const token0 = stakingInfo.tokens[0]
|
||||
const token1 = stakingInfo.tokens[1]
|
||||
|
||||
const currency0 = unwrappedToken(token0)
|
||||
const currency1 = unwrappedToken(token1)
|
||||
|
||||
const isStaking = Boolean(stakingInfo.stakedAmount.greaterThan('0'))
|
||||
|
||||
// get the color of the token
|
||||
const token = currency0.isNative ? token1 : token0
|
||||
const WETH = currency0.isNative ? token0 : token1
|
||||
const backgroundColor = useColor(token)
|
||||
|
||||
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo.stakedAmount.currency)
|
||||
const [, stakingTokenPair] = useV2Pair(...stakingInfo.tokens)
|
||||
|
||||
// let returnOverMonth: Percent = new Percent('0')
|
||||
let valueOfTotalStakedAmountInWETH: CurrencyAmount<Token> | undefined
|
||||
if (totalSupplyOfStakingToken && stakingTokenPair) {
|
||||
// take the total amount of LP tokens staked, multiply by ETH value of all LP tokens, divide by all LP tokens
|
||||
valueOfTotalStakedAmountInWETH = CurrencyAmount.fromRawAmount(
|
||||
WETH,
|
||||
JSBI.divide(
|
||||
JSBI.multiply(
|
||||
JSBI.multiply(stakingInfo.totalStakedAmount.quotient, stakingTokenPair.reserveOf(WETH).quotient),
|
||||
JSBI.BigInt(2) // this is b/c the value of LP shares are ~double the value of the WETH they entitle owner to
|
||||
),
|
||||
totalSupplyOfStakingToken.quotient
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// get the USD value of staked WETH
|
||||
const USDPrice = useStablecoinPrice(WETH)
|
||||
const valueOfTotalStakedAmountInUSDC =
|
||||
valueOfTotalStakedAmountInWETH && USDPrice?.quote(valueOfTotalStakedAmountInWETH)
|
||||
|
||||
return (
|
||||
<Wrapper showBackground={isStaking} bgColor={backgroundColor}>
|
||||
<CardBGImage desaturate />
|
||||
<CardNoise />
|
||||
|
||||
<TopSection>
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={24} />
|
||||
<ThemedText.DeprecatedWhite fontWeight={600} fontSize={24} style={{ marginLeft: '8px' }}>
|
||||
{currency0.symbol}-{currency1.symbol}
|
||||
</ThemedText.DeprecatedWhite>
|
||||
|
||||
<StyledInternalLink to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`} style={{ width: '100%' }}>
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px">
|
||||
{isStaking ? <Trans>Manage</Trans> : <Trans>Deposit</Trans>}
|
||||
</ButtonPrimary>
|
||||
</StyledInternalLink>
|
||||
</TopSection>
|
||||
|
||||
<StatContainer>
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedWhite>
|
||||
<Trans>Total deposited</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
<ThemedText.DeprecatedWhite>
|
||||
{valueOfTotalStakedAmountInUSDC ? (
|
||||
<Trans>${valueOfTotalStakedAmountInUSDC.toFixed(0, { groupSeparator: ',' })}</Trans>
|
||||
) : (
|
||||
<Trans>{valueOfTotalStakedAmountInWETH?.toSignificant(4, { groupSeparator: ',' }) ?? '-'} ETH</Trans>
|
||||
)}
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedWhite>
|
||||
<Trans>Pool rate</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
<ThemedText.DeprecatedWhite>
|
||||
{stakingInfo ? (
|
||||
stakingInfo.active ? (
|
||||
<Trans>
|
||||
{stakingInfo.totalRewardRate?.multiply(BIG_INT_SECONDS_IN_WEEK)?.toFixed(0, { groupSeparator: ',' })}{' '}
|
||||
UNI / week
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>0 UNI / week</Trans>
|
||||
)
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
</StatContainer>
|
||||
|
||||
{isStaking && (
|
||||
<>
|
||||
<Break />
|
||||
<BottomSection showBackground={true}>
|
||||
<ThemedText.DeprecatedBlack color="white" fontWeight={500}>
|
||||
<span>
|
||||
<Trans>Your rate</Trans>
|
||||
</span>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
|
||||
<ThemedText.DeprecatedBlack style={{ textAlign: 'right' }} color="white" fontWeight={500}>
|
||||
<span role="img" aria-label="wizard-icon" style={{ marginRight: '0.5rem' }}>
|
||||
⚡
|
||||
</span>
|
||||
{stakingInfo ? (
|
||||
stakingInfo.active ? (
|
||||
<Trans>
|
||||
{stakingInfo.rewardRate
|
||||
?.multiply(BIG_INT_SECONDS_IN_WEEK)
|
||||
?.toSignificant(4, { groupSeparator: ',' })}{' '}
|
||||
UNI / week
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>0 UNI / week</Trans>
|
||||
)
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</BottomSection>
|
||||
</>
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useV2LiquidityTokenPermit } from 'hooks/useV2LiquidityTokenPermit'
|
||||
import { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
|
||||
import { useContract, usePairContract, useV2RouterContract } from '../../hooks/useContract'
|
||||
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
|
||||
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { formatCurrencyAmount } from '../../utils/formatCurrencyAmount'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
import { ButtonConfirmed, ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyInputPanel from '../CurrencyInputPanel'
|
||||
import Modal from '../Modal'
|
||||
import { LoadingView, SubmittedView } from '../ModalViews'
|
||||
import ProgressCircles from '../ProgressSteps'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
|
||||
|
||||
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
const HypotheticalRewardRate = styled.div<{ dim: boolean }>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
|
||||
opacity: ${({ dim }) => (dim ? 0.5 : 1)};
|
||||
`
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
interface StakingModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
stakingInfo: StakingInfo
|
||||
userLiquidityUnstaked: CurrencyAmount<Token> | undefined
|
||||
}
|
||||
|
||||
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
|
||||
const { provider } = useWeb3React()
|
||||
|
||||
// track and parse user input
|
||||
const [typedValue, setTypedValue] = useState('')
|
||||
const { parsedAmount, error } = useDerivedStakeInfo(
|
||||
typedValue,
|
||||
stakingInfo.stakedAmount.currency,
|
||||
userLiquidityUnstaked
|
||||
)
|
||||
const parsedAmountWrapped = parsedAmount?.wrapped
|
||||
|
||||
let hypotheticalRewardRate: CurrencyAmount<Token> = CurrencyAmount.fromRawAmount(stakingInfo.rewardRate.currency, '0')
|
||||
if (parsedAmountWrapped?.greaterThan('0')) {
|
||||
hypotheticalRewardRate = stakingInfo.getHypotheticalRewardRate(
|
||||
stakingInfo.stakedAmount.add(parsedAmountWrapped),
|
||||
stakingInfo.totalStakedAmount.add(parsedAmountWrapped),
|
||||
stakingInfo.totalRewardRate
|
||||
)
|
||||
}
|
||||
|
||||
// state for pending and submitted txn views
|
||||
const addTransaction = useTransactionAdder()
|
||||
const [attempting, setAttempting] = useState<boolean>(false)
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const wrappedOnDismiss = useCallback(() => {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}, [onDismiss])
|
||||
|
||||
// pair contract for this token to be staked
|
||||
const dummyPair = new Pair(
|
||||
CurrencyAmount.fromRawAmount(stakingInfo.tokens[0], '0'),
|
||||
CurrencyAmount.fromRawAmount(stakingInfo.tokens[1], '0')
|
||||
)
|
||||
const pairContract = usePairContract(dummyPair.liquidityToken.address)
|
||||
|
||||
// approval data for stake
|
||||
const deadline = useTransactionDeadline()
|
||||
const router = useV2RouterContract()
|
||||
const { signatureData, gatherPermitSignature } = useV2LiquidityTokenPermit(parsedAmountWrapped, router?.address)
|
||||
const [approval, approveCallback] = useApproveCallback(parsedAmount, stakingInfo.stakingRewardAddress)
|
||||
|
||||
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
|
||||
async function onStake() {
|
||||
setAttempting(true)
|
||||
if (stakingContract && parsedAmount && deadline) {
|
||||
if (approval === ApprovalState.APPROVED) {
|
||||
await stakingContract.stake(`0x${parsedAmount.quotient.toString(16)}`, { gasLimit: 350000 })
|
||||
} else if (signatureData) {
|
||||
stakingContract
|
||||
.stakeWithPermit(
|
||||
`0x${parsedAmount.quotient.toString(16)}`,
|
||||
signatureData.deadline,
|
||||
signatureData.v,
|
||||
signatureData.r,
|
||||
signatureData.s,
|
||||
{ gasLimit: 350000 }
|
||||
)
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
type: TransactionType.DEPOSIT_LIQUIDITY_STAKING,
|
||||
token0Address: stakingInfo.tokens[0].address,
|
||||
token1Address: stakingInfo.tokens[1].address,
|
||||
})
|
||||
setHash(response.hash)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
} else {
|
||||
setAttempting(false)
|
||||
throw new Error('Attempting to stake without approval or a signature. Please contact support.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wrapped onUserInput to clear signatures
|
||||
const onUserInput = useCallback((typedValue: string) => {
|
||||
setTypedValue(typedValue)
|
||||
}, [])
|
||||
|
||||
// used for max input button
|
||||
const maxAmountInput = maxAmountSpend(userLiquidityUnstaked)
|
||||
const atMaxAmount = Boolean(maxAmountInput && parsedAmount?.equalTo(maxAmountInput))
|
||||
const handleMax = useCallback(() => {
|
||||
maxAmountInput && onUserInput(maxAmountInput.toExact())
|
||||
}, [maxAmountInput, onUserInput])
|
||||
|
||||
async function onAttemptToApprove() {
|
||||
if (!pairContract || !provider || !deadline) throw new Error('missing dependencies')
|
||||
if (!parsedAmount) throw new Error('missing liquidity amount')
|
||||
|
||||
if (gatherPermitSignature) {
|
||||
try {
|
||||
await gatherPermitSignature()
|
||||
} catch (error) {
|
||||
// try to approve if gatherPermitSignature failed for any reason other than the user rejecting it
|
||||
if (error?.code !== 4001) {
|
||||
await approveCallback()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await approveCallback()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Deposit</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<CloseIcon onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<CurrencyInputPanel
|
||||
value={typedValue}
|
||||
onUserInput={onUserInput}
|
||||
onMax={handleMax}
|
||||
showMaxButton={!atMaxAmount}
|
||||
currency={stakingInfo.stakedAmount.currency}
|
||||
pair={dummyPair}
|
||||
label=""
|
||||
renderBalance={(amount) => <Trans>Available to deposit: {formatCurrencyAmount(amount, 4)}</Trans>}
|
||||
id="stake-liquidity-token"
|
||||
/>
|
||||
|
||||
<HypotheticalRewardRate dim={!hypotheticalRewardRate.greaterThan('0')}>
|
||||
<div>
|
||||
<ThemedText.DeprecatedBlack fontWeight={600}>
|
||||
<Trans>Weekly Rewards</Trans>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</div>
|
||||
|
||||
<ThemedText.DeprecatedBlack>
|
||||
<Trans>
|
||||
{hypotheticalRewardRate
|
||||
.multiply((60 * 60 * 24 * 7).toString())
|
||||
.toSignificant(4, { groupSeparator: ',' })}{' '}
|
||||
UNI / week
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</HypotheticalRewardRate>
|
||||
|
||||
<RowBetween>
|
||||
<ButtonConfirmed
|
||||
mr="0.5rem"
|
||||
onClick={onAttemptToApprove}
|
||||
confirmed={approval === ApprovalState.APPROVED || signatureData !== null}
|
||||
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
|
||||
>
|
||||
<Trans>Approve</Trans>
|
||||
</ButtonConfirmed>
|
||||
<ButtonError
|
||||
disabled={!!error || (signatureData === null && approval !== ApprovalState.APPROVED)}
|
||||
error={!!error && !!parsedAmount}
|
||||
onClick={onStake}
|
||||
>
|
||||
{error ?? <Trans>Deposit</Trans>}
|
||||
</ButtonError>
|
||||
</RowBetween>
|
||||
<ProgressCircles steps={[approval === ApprovalState.APPROVED || signatureData !== null]} disabled={true} />
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<ThemedText.DeprecatedLargeHeader>
|
||||
<Trans>Depositing Liquidity</Trans>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>{parsedAmount?.toSignificant(4)} UNI-V2</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</LoadingView>
|
||||
)}
|
||||
{attempting && hash && (
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<ThemedText.DeprecatedLargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Deposited {parsedAmount?.toSignificant(4)} UNI-V2</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</SubmittedView>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import FormattedCurrencyAmount from '../FormattedCurrencyAmount'
|
||||
import Modal from '../Modal'
|
||||
import { LoadingView, SubmittedView } from '../ModalViews'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
|
||||
|
||||
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
interface StakingModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
stakingInfo: StakingInfo
|
||||
}
|
||||
|
||||
export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: StakingModalProps) {
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const addTransaction = useTransactionAdder()
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState(false)
|
||||
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
|
||||
|
||||
async function onWithdraw() {
|
||||
if (stakingContract && stakingInfo?.stakedAmount) {
|
||||
setAttempting(true)
|
||||
await stakingContract
|
||||
.exit({ gasLimit: 300000 })
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
type: TransactionType.WITHDRAW_LIQUIDITY_STAKING,
|
||||
token0Address: stakingInfo.tokens[0].address,
|
||||
token1Address: stakingInfo.tokens[1].address,
|
||||
})
|
||||
setHash(response.hash)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let error: ReactNode | undefined
|
||||
if (!account) {
|
||||
error = <Trans>Connect a wallet</Trans>
|
||||
}
|
||||
if (!stakingInfo?.stakedAmount) {
|
||||
error = error ?? <Trans>Enter an amount</Trans>
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Withdraw</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<CloseIcon onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
{stakingInfo?.stakedAmount && (
|
||||
<AutoColumn justify="center" gap="md">
|
||||
<ThemedText.HeadlineLarge>
|
||||
<FormattedCurrencyAmount currencyAmount={stakingInfo.stakedAmount} />
|
||||
</ThemedText.HeadlineLarge>
|
||||
<ThemedText.DeprecatedBody>
|
||||
<Trans>Deposited liquidity:</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
)}
|
||||
{stakingInfo?.earnedAmount && (
|
||||
<AutoColumn justify="center" gap="md">
|
||||
<ThemedText.HeadlineLarge>
|
||||
<FormattedCurrencyAmount currencyAmount={stakingInfo?.earnedAmount} />
|
||||
</ThemedText.HeadlineLarge>
|
||||
<ThemedText.DeprecatedBody>
|
||||
<Trans>Unclaimed UNI</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
)}
|
||||
<ThemedText.DeprecatedSubHeader style={{ textAlign: 'center' }}>
|
||||
<Trans>When you withdraw, your UNI is claimed and your liquidity is removed from the mining pool.</Trans>
|
||||
</ThemedText.DeprecatedSubHeader>
|
||||
<ButtonError disabled={!!error} error={!!error && !!stakingInfo?.stakedAmount} onClick={onWithdraw}>
|
||||
{error ?? <Trans>Withdraw & Claim</Trans>}
|
||||
</ButtonError>
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Withdrawing {stakingInfo?.stakedAmount?.toSignificant(4)} UNI-V2</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Claiming {stakingInfo?.earnedAmount?.toSignificant(4)} UNI</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</LoadingView>
|
||||
)}
|
||||
{hash && (
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<ThemedText.DeprecatedLargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Withdrew UNI-V2!</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedBody fontSize={20}>
|
||||
<Trans>Claimed UNI!</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</SubmittedView>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -110,19 +110,10 @@ interface SwapDetailsInlineProps {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
||||
syncing: boolean
|
||||
loading: boolean
|
||||
showInverted: boolean
|
||||
setShowInverted: React.Dispatch<React.SetStateAction<boolean>>
|
||||
allowedSlippage: Percent
|
||||
}
|
||||
|
||||
export default function SwapDetailsDropdown({
|
||||
trade,
|
||||
syncing,
|
||||
loading,
|
||||
showInverted,
|
||||
setShowInverted,
|
||||
allowedSlippage,
|
||||
}: SwapDetailsInlineProps) {
|
||||
export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSlippage }: SwapDetailsInlineProps) {
|
||||
const theme = useTheme()
|
||||
const { chainId } = useWeb3React()
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
@@ -169,11 +160,7 @@ export default function SwapDetailsDropdown({
|
||||
)}
|
||||
{trade ? (
|
||||
<LoadingOpacityContainer $loading={syncing}>
|
||||
<TradePrice
|
||||
price={trade.executionPrice}
|
||||
showInverted={showInverted}
|
||||
setShowInverted={setShowInverted}
|
||||
/>
|
||||
<TradePrice price={trade.executionPrice} />
|
||||
</LoadingOpacityContainer>
|
||||
) : loading || syncing ? (
|
||||
<ThemedText.DeprecatedMain fontSize={14}>
|
||||
|
||||
@@ -75,7 +75,6 @@ export default function SwapModalHeader({
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
|
||||
const [priceUpdate, setPriceUpdate] = useState<number | undefined>()
|
||||
|
||||
@@ -153,7 +152,7 @@ export default function SwapModalHeader({
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
<RowBetween style={{ marginTop: '0.25rem', padding: '0 1rem' }}>
|
||||
<TradePrice price={trade.executionPrice} showInverted={showInverted} setShowInverted={setShowInverted} />
|
||||
<TradePrice price={trade.executionPrice} />
|
||||
</RowBetween>
|
||||
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
|
||||
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price } from '@uniswap/sdk-core'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import { useCallback } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { formatDollar, formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
|
||||
|
||||
interface TradePriceProps {
|
||||
price: Price<Currency, Currency>
|
||||
showInverted: boolean
|
||||
setShowInverted: (showInverted: boolean) => void
|
||||
}
|
||||
|
||||
const StyledPriceContainer = styled.button`
|
||||
@@ -30,8 +27,8 @@ const StyledPriceContainer = styled.button`
|
||||
user-select: text;
|
||||
`
|
||||
|
||||
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
|
||||
const theme = useTheme()
|
||||
export default function TradePrice({ price }: TradePriceProps) {
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
|
||||
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
|
||||
@@ -58,9 +55,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
}}
|
||||
title={text}
|
||||
>
|
||||
<Text fontWeight={500} color={theme.textPrimary}>
|
||||
{text}
|
||||
</Text>{' '}
|
||||
<ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '}
|
||||
{usdcPrice && (
|
||||
<ThemedText.DeprecatedDarkGray>
|
||||
<Trans>({formatDollar({ num: priceToPreciseFloat(usdcPrice), isPrice: true })})</Trans>
|
||||
|
||||
@@ -11,9 +11,6 @@ export const L2_DEADLINE_FROM_NOW = 60 * 5
|
||||
export const DEFAULT_TXN_DISMISS_MS = 25000
|
||||
export const L2_TXN_DISMISS_MS = 5000
|
||||
|
||||
// used for rewards deadlines
|
||||
export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)
|
||||
|
||||
export const BIG_INT_ZERO = JSBI.BigInt(0)
|
||||
|
||||
// one basis JSBI.BigInt
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export enum FeatureFlag {
|
||||
fiatOnramp = 'fiatOnramp',
|
||||
traceJsonRpc = 'traceJsonRpc',
|
||||
permit2 = 'permit2',
|
||||
landingRedirect = 'landingRedirect',
|
||||
}
|
||||
|
||||
6
src/featureFlags/flags/fiatOnramp.ts
Normal file
6
src/featureFlags/flags/fiatOnramp.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { BaseVariant } from '../index'
|
||||
|
||||
export function useFiatOnrampFlag(): BaseVariant {
|
||||
return BaseVariant.Enabled
|
||||
// return useBaseFlag(FeatureFlag.fiatOnramp)
|
||||
}
|
||||
7
src/featureFlags/flags/landingRedirect.ts
Normal file
7
src/featureFlags/flags/landingRedirect.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useLandingRedirectFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.landingRedirect)
|
||||
}
|
||||
|
||||
export { BaseVariant as LandingRedirectVariant }
|
||||
@@ -1,120 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
import Modal from 'components/Modal'
|
||||
import { useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme/components'
|
||||
import { ThemedText } from 'theme/components/text'
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 30% 24px 24px;
|
||||
overflow: hidden;
|
||||
height: fit-content;
|
||||
user-select: none;
|
||||
`
|
||||
|
||||
const CloseButton = styled(X)`
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 24px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const Background = styled.img`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const Link = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.accentActive};
|
||||
stroke: ${({ theme }) => theme.accentActive};
|
||||
`
|
||||
|
||||
const Title = styled(ThemedText.LargeHeader)`
|
||||
@media (max-width: ${({ theme }) => theme.breakpoint.xl}px) {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
`
|
||||
|
||||
const Paragraph = styled(ThemedText.BodySecondary)`
|
||||
line-height: 24px;
|
||||
|
||||
@media (max-width: ${({ theme }) => theme.breakpoint.xl}px) {
|
||||
font-size: 14px !important;
|
||||
line-height: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const BACKGROUND_IMAGE = {
|
||||
dark: {
|
||||
src: require('../../../assets/images/welcomeModal-dark.jpg').default,
|
||||
srcSet: `
|
||||
${require('../../../assets/images/welcomeModal-dark@2x.jpg').default} 2x,
|
||||
${require('../../../assets/images/welcomeModal-dark@3x.jpg').default} 3x,
|
||||
`,
|
||||
},
|
||||
light: {
|
||||
src: require('../../../assets/images/welcomeModal-light.jpg').default,
|
||||
srcSet: `
|
||||
${require('../../../assets/images/welcomeModal-light@2x.jpg').default} 2x,
|
||||
${require('../../../assets/images/welcomeModal-light@3x.jpg').default} 3x,
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
export function WelcomeModal({ onDismissed }: { onDismissed: () => void }) {
|
||||
const [isOpen, setIsOpen] = useState(true)
|
||||
|
||||
const dismiss = () => {
|
||||
setIsOpen(false)
|
||||
setTimeout(() => onDismissed())
|
||||
}
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onSwipe={dismiss} maxWidth={720} isBottomSheet={false}>
|
||||
<Container data-testid="nft-welcome-modal">
|
||||
<Background
|
||||
{...(theme.darkMode ? BACKGROUND_IMAGE.dark : BACKGROUND_IMAGE.light)}
|
||||
alt="Welcome modal background"
|
||||
draggable={false}
|
||||
/>
|
||||
<Content>
|
||||
<Title>Introducing NFTs on Uniswap</Title>
|
||||
<Paragraph>
|
||||
You can now buy and sell NFTs on Uniswap across marketplaces. Trade here to find more listings and better
|
||||
prices. <br />
|
||||
<br />
|
||||
NFTs on Uniswap replaces Genie, which was{' '}
|
||||
<Link href="https://uniswap.org/blog/genie" title="Uniswap Labs has acquired Genie">
|
||||
acquired{' '}
|
||||
</Link>{' '}
|
||||
by Uniswap Labs earlier this year. If you have used Genie in the past, you may be eligible for a USDC
|
||||
airdrop.{' '}
|
||||
<Link
|
||||
href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement"
|
||||
title="Uniswap NFT aggregator announcement"
|
||||
>
|
||||
Learn more.
|
||||
</Link>
|
||||
</Paragraph>
|
||||
<CloseButton data-testid="nft-intro-modal" size={24} onClick={dismiss} />
|
||||
</Content>
|
||||
</Container>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { LARGE_MEDIA_BREAKPOINT, SMALL_MOBILE_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { X } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useHideNftPromoBanner } from 'state/user/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ClickableStyle } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import nftPromoImage1 from '../nftExploreBanner/nftArt1.png'
|
||||
import nftPromoImage2 from '../nftExploreBanner/nftArt2.png'
|
||||
import nftPromoImage3 from '../nftExploreBanner/nftArt3.png'
|
||||
|
||||
function getRandom(list: any[]) {
|
||||
return list[Math.floor(Math.random() * list.length)]
|
||||
}
|
||||
const randomizedNftImage = getRandom([nftPromoImage1, nftPromoImage2, nftPromoImage3])
|
||||
|
||||
const PopupContainer = styled.div<{ show: boolean }>`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
display: ${({ show }) => (show ? 'flex' : 'none')};
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
right: clamp(0px, 1vw, 16px);
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.slow} opacity ${timing.in}`};
|
||||
width: 98vw;
|
||||
bottom: 55px;
|
||||
@media screen and (min-width: ${LARGE_MEDIA_BREAKPOINT}) {
|
||||
bottom: 48px;
|
||||
}
|
||||
@media screen and (min-width: ${SMALL_MOBILE_MEDIA_BREAKPOINT}) {
|
||||
width: 391px;
|
||||
}
|
||||
:hover {
|
||||
border: double 1px transparent;
|
||||
border-radius: 12px;
|
||||
background-image: ${({ theme }) =>
|
||||
`linear-gradient(${theme.backgroundSurface}, ${theme.backgroundSurface}),
|
||||
radial-gradient(circle at top left, hsla(299, 100%, 87%, 1), hsla(299, 100%, 61%, 1))`};
|
||||
background-origin: border-box;
|
||||
background-clip: padding-box, border-box;
|
||||
}
|
||||
`
|
||||
|
||||
const InnerContainer = styled.div`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
|
||||
const StyledXButton = styled(X)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledImageContainer = styled(Box)`
|
||||
width: 20%;
|
||||
cursor: pointer;
|
||||
aspectratio: 1;
|
||||
transition: transform 0.25s ease 0s;
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const LinkStyle = css`
|
||||
color: ${({ theme }) => theme.accentActive};
|
||||
stroke: ${({ theme }) => theme.accentActive};
|
||||
`
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
${ClickableStyle}
|
||||
${LinkStyle}
|
||||
`
|
||||
|
||||
export default function NftExploreBanner() {
|
||||
const [hideNftPromoBanner, toggleHideNftPromoBanner] = useHideNftPromoBanner()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const navigateToNfts = () => {
|
||||
navigate('/nfts')
|
||||
toggleHideNftPromoBanner()
|
||||
}
|
||||
|
||||
return (
|
||||
<PopupContainer show={!hideNftPromoBanner} onClick={navigateToNfts}>
|
||||
<InnerContainer>
|
||||
<StyledImageContainer as="img" src={randomizedNftImage} draggable={false} />
|
||||
<TextContainer>
|
||||
{/* <HeaderText> */}
|
||||
<div className={subhead}>
|
||||
<Trans>Introducing NFTs on Uniswap</Trans>
|
||||
</div>
|
||||
{/* </HeaderText> */}
|
||||
|
||||
{/* <Description> */}
|
||||
<div className={bodySmall}>
|
||||
<Trans>Buy and sell NFTs across more listings at better prices.</Trans>{' '}
|
||||
<StyledLink to="/nfts">
|
||||
<Trans>Explore NFTs</Trans>
|
||||
</StyledLink>{' '}
|
||||
</div>
|
||||
</TextContainer>
|
||||
{/* </Description> */}
|
||||
<StyledXButton
|
||||
size={20}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleHideNftPromoBanner()
|
||||
}}
|
||||
/>
|
||||
</InnerContainer>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
@@ -34,7 +34,7 @@ const ListingHeader = styled(Row)`
|
||||
margin-top: 18px;
|
||||
|
||||
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
margin-top: 40px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -10,9 +10,21 @@ import { LOOKS_RARE_CREATOR_BASIS_POINTS } from 'nft/utils'
|
||||
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import { Dispatch, FormEvent, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import * as styles from './ListPage.css'
|
||||
|
||||
const TableHeader = styled.div`
|
||||
display: flex;
|
||||
position: sticky;
|
||||
align-items: center;
|
||||
top: 72px;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
z-index: 1;
|
||||
background-color: ${({ theme }) => theme.backgroundBackdrop};
|
||||
`
|
||||
|
||||
enum SetPriceMethod {
|
||||
SAME_PRICE,
|
||||
FLOOR_PRICE,
|
||||
@@ -44,7 +56,7 @@ export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingM
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<Row marginTop="20">
|
||||
<TableHeader>
|
||||
<Column
|
||||
marginLeft={selectedMarkets.length > 1 ? '36' : '0'}
|
||||
transition="500"
|
||||
@@ -86,7 +98,7 @@ export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingM
|
||||
You receive
|
||||
</Column>
|
||||
</Row>
|
||||
</Row>
|
||||
</TableHeader>
|
||||
{sellAssets.map((asset) => {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -2,10 +2,8 @@ import { Trace } from '@uniswap/analytics'
|
||||
import { PageName } from '@uniswap/analytics-events'
|
||||
import Banner from 'nft/components/explore/Banner'
|
||||
import TrendingCollections from 'nft/components/explore/TrendingCollections'
|
||||
import { WelcomeModal } from 'nft/components/explore/WelcomeModal'
|
||||
import { useBag } from 'nft/hooks'
|
||||
import { useEffect } from 'react'
|
||||
import { useHideNFTWelcomeModal } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const ExploreContainer = styled.div`
|
||||
@@ -25,7 +23,6 @@ const ExploreContainer = styled.div`
|
||||
|
||||
const NftExplore = () => {
|
||||
const setBagExpanded = useBag((state) => state.setBagExpanded)
|
||||
const [isModalHidden, hideModal] = useHideNFTWelcomeModal()
|
||||
|
||||
useEffect(() => {
|
||||
setBagExpanded({ bagExpanded: false, manualClose: false })
|
||||
@@ -38,7 +35,6 @@ const NftExplore = () => {
|
||||
<Banner />
|
||||
<TrendingCollections />
|
||||
</ExploreContainer>
|
||||
{!isModalHidden && <WelcomeModal onDismissed={hideModal} />}
|
||||
</Trace>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -31,8 +31,6 @@ import About from './About'
|
||||
import AddLiquidity from './AddLiquidity'
|
||||
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
|
||||
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
|
||||
import Earn from './Earn'
|
||||
import Manage from './Earn/Manage'
|
||||
import Landing from './Landing'
|
||||
import MigrateV2 from './MigrateV2'
|
||||
import MigrateV2Pair from './MigrateV2/MigrateV2Pair'
|
||||
@@ -44,7 +42,7 @@ import PoolFinder from './PoolFinder'
|
||||
import RemoveLiquidity from './RemoveLiquidity'
|
||||
import RemoveLiquidityV3 from './RemoveLiquidity/V3'
|
||||
import Swap from './Swap'
|
||||
import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly } from './Swap/redirects'
|
||||
import { RedirectPathToSwapOnly } from './Swap/redirects'
|
||||
import Tokens from './Tokens'
|
||||
|
||||
const TokenDetails = lazy(() => import('./TokenDetails'))
|
||||
@@ -102,7 +100,7 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>`
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
z-index: ${Z_INDEX.dropdown};
|
||||
`
|
||||
|
||||
function getCurrentPageFromLocation(locationPathname: string): PageName | undefined {
|
||||
@@ -218,9 +216,6 @@ export default function App() {
|
||||
}
|
||||
/>
|
||||
<Route path="create-proposal" element={<Navigate to="/vote/create-proposal" replace />} />
|
||||
<Route path="claim" element={<OpenClaimAddressModalAndRedirectToSwap />} />
|
||||
<Route path="uni" element={<Earn />} />
|
||||
<Route path="uni/:currencyIdA/:currencyIdB" element={<Manage />} />
|
||||
|
||||
<Route path="send" element={<RedirectPathToSwapOnly />} />
|
||||
<Route path="swap" element={<Swap />} />
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { REWARDS_DURATION_DAYS, STAKING_GENESIS } from '../../state/stake/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
|
||||
const MINUTE = 60
|
||||
const HOUR = MINUTE * 60
|
||||
const DAY = HOUR * 24
|
||||
const REWARDS_DURATION = DAY * REWARDS_DURATION_DAYS
|
||||
|
||||
export function Countdown({ exactEnd }: { exactEnd?: Date }) {
|
||||
// get end/beginning times
|
||||
const end = useMemo(
|
||||
() => (exactEnd ? Math.floor(exactEnd.getTime() / 1000) : STAKING_GENESIS + REWARDS_DURATION),
|
||||
[exactEnd]
|
||||
)
|
||||
const begin = useMemo(() => end - REWARDS_DURATION, [end])
|
||||
|
||||
// get current time
|
||||
const [time, setTime] = useState(() => Math.floor(Date.now() / 1000))
|
||||
useEffect((): (() => void) | void => {
|
||||
// we only need to tick if rewards haven't ended yet
|
||||
if (time <= end) {
|
||||
const timeout = setTimeout(() => setTime(Math.floor(Date.now() / 1000)), 1000)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [time, end])
|
||||
|
||||
const timeUntilGenesis = begin - time
|
||||
const timeUntilEnd = end - time
|
||||
|
||||
let timeRemaining: number
|
||||
let message: string
|
||||
if (timeUntilGenesis >= 0) {
|
||||
message = 'Rewards begin in'
|
||||
timeRemaining = timeUntilGenesis
|
||||
} else {
|
||||
const ongoing = timeUntilEnd >= 0
|
||||
if (ongoing) {
|
||||
message = 'Rewards end in'
|
||||
timeRemaining = timeUntilEnd
|
||||
} else {
|
||||
message = 'Rewards have ended!'
|
||||
timeRemaining = Infinity
|
||||
}
|
||||
}
|
||||
|
||||
const days = (timeRemaining - (timeRemaining % DAY)) / DAY
|
||||
timeRemaining -= days * DAY
|
||||
const hours = (timeRemaining - (timeRemaining % HOUR)) / HOUR
|
||||
timeRemaining -= hours * HOUR
|
||||
const minutes = (timeRemaining - (timeRemaining % MINUTE)) / MINUTE
|
||||
timeRemaining -= minutes * MINUTE
|
||||
const seconds = timeRemaining
|
||||
|
||||
return (
|
||||
<ThemedText.DeprecatedBlack fontWeight={400}>
|
||||
{message}{' '}
|
||||
{Number.isFinite(timeRemaining) && (
|
||||
<code>
|
||||
{`${days}:${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds
|
||||
.toString()
|
||||
.padStart(2, '0')}`}
|
||||
</code>
|
||||
)}
|
||||
</ThemedText.DeprecatedBlack>
|
||||
)
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import JSBI from 'jsbi'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CountUp } from 'use-count-up'
|
||||
|
||||
import { ButtonEmpty, ButtonPrimary } from '../../components/Button'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import ClaimRewardModal from '../../components/earn/ClaimRewardModal'
|
||||
import StakingModal from '../../components/earn/StakingModal'
|
||||
import { CardBGImage, CardNoise, CardSection, DataCard } from '../../components/earn/styled'
|
||||
import UnstakingModal from '../../components/earn/UnstakingModal'
|
||||
import { RowBetween } from '../../components/Row'
|
||||
import { BIG_INT_SECONDS_IN_WEEK, BIG_INT_ZERO } from '../../constants/misc'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
import usePrevious from '../../hooks/usePrevious'
|
||||
import useStablecoinPrice from '../../hooks/useStablecoinPrice'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useV2Pair } from '../../hooks/useV2Pairs'
|
||||
import { useToggleWalletModal } from '../../state/application/hooks'
|
||||
import { useTokenBalance } from '../../state/connection/hooks'
|
||||
import { useStakingInfo } from '../../state/stake/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
padding: 68px 8px 0px;
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||
padding: 48px 8px 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
padding-top: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const PositionInfo = styled(AutoColumn)<{ dim: any }>`
|
||||
position: relative;
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
opacity: ${({ dim }) => (dim ? 0.6 : 1)};
|
||||
`
|
||||
|
||||
const BottomSection = styled(AutoColumn)`
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const StyledDataCard = styled(DataCard)<{ bgColor?: any; showBackground?: any }>`
|
||||
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #1e1a31 0%, #3d51a5 100%);
|
||||
z-index: 2;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
background: ${({ theme, bgColor, showBackground }) =>
|
||||
`radial-gradient(91.85% 100% at 1.84% 0%, ${bgColor} 0%, ${
|
||||
showBackground ? theme.black : theme.deprecated_bg5
|
||||
} 100%) `};
|
||||
`
|
||||
|
||||
const StyledBottomCard = styled(DataCard)<{ dim: any }>`
|
||||
background: ${({ theme }) => theme.deprecated_bg3};
|
||||
opacity: ${({ dim }) => (dim ? 0.4 : 1)};
|
||||
margin-top: -40px;
|
||||
padding: 0 1.25rem 1rem 1.25rem;
|
||||
padding-top: 32px;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const PoolData = styled(DataCard)`
|
||||
background: none;
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg4};
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const VoteCard = styled(DataCard)`
|
||||
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #27ae60 0%, #000000 100%);
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const DataRow = styled(RowBetween)`
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function Manage() {
|
||||
const { currencyIdA, currencyIdB } = useParams<{ currencyIdA: string; currencyIdB: string }>()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// get currencies and pair
|
||||
const [currencyA, currencyB] = [useCurrency(currencyIdA), useCurrency(currencyIdB)]
|
||||
const tokenA = (currencyA ?? undefined)?.wrapped
|
||||
const tokenB = (currencyB ?? undefined)?.wrapped
|
||||
|
||||
const [, stakingTokenPair] = useV2Pair(tokenA, tokenB)
|
||||
const stakingInfo = useStakingInfo(stakingTokenPair)?.[0]
|
||||
|
||||
// detect existing unstaked LP position to show add button if none found
|
||||
const userLiquidityUnstaked = useTokenBalance(account ?? undefined, stakingInfo?.stakedAmount?.currency)
|
||||
const showAddLiquidityButton = Boolean(stakingInfo?.stakedAmount?.equalTo('0') && userLiquidityUnstaked?.equalTo('0'))
|
||||
|
||||
// toggle for staking modal and unstaking modal
|
||||
const [showStakingModal, setShowStakingModal] = useState(false)
|
||||
const [showUnstakingModal, setShowUnstakingModal] = useState(false)
|
||||
const [showClaimRewardModal, setShowClaimRewardModal] = useState(false)
|
||||
|
||||
// fade cards if nothing staked or nothing earned yet
|
||||
const disableTop = !stakingInfo?.stakedAmount || stakingInfo.stakedAmount.equalTo(JSBI.BigInt(0))
|
||||
|
||||
const token = currencyA?.isNative ? tokenB : tokenA
|
||||
const WETH = currencyA?.isNative ? tokenA : tokenB
|
||||
const backgroundColor = useColor(token)
|
||||
|
||||
// get WETH value of staked LP tokens
|
||||
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo?.stakedAmount?.currency)
|
||||
let valueOfTotalStakedAmountInWETH: CurrencyAmount<Token> | undefined
|
||||
if (totalSupplyOfStakingToken && stakingTokenPair && stakingInfo && WETH) {
|
||||
// take the total amount of LP tokens staked, multiply by ETH value of all LP tokens, divide by all LP tokens
|
||||
valueOfTotalStakedAmountInWETH = CurrencyAmount.fromRawAmount(
|
||||
WETH,
|
||||
JSBI.divide(
|
||||
JSBI.multiply(
|
||||
JSBI.multiply(stakingInfo.totalStakedAmount.quotient, stakingTokenPair.reserveOf(WETH).quotient),
|
||||
JSBI.BigInt(2) // this is b/c the value of LP shares are ~double the value of the WETH they entitle owner to
|
||||
),
|
||||
totalSupplyOfStakingToken.quotient
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const countUpAmount = stakingInfo?.earnedAmount?.toFixed(6) ?? '0'
|
||||
const countUpAmountPrevious = usePrevious(countUpAmount) ?? '0'
|
||||
|
||||
// get the USD value of staked WETH
|
||||
const USDPrice = useStablecoinPrice(WETH)
|
||||
const valueOfTotalStakedAmountInUSDC =
|
||||
valueOfTotalStakedAmountInWETH && USDPrice?.quote(valueOfTotalStakedAmountInWETH)
|
||||
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
|
||||
const handleDepositClick = useCallback(() => {
|
||||
if (account) {
|
||||
setShowStakingModal(true)
|
||||
} else {
|
||||
toggleWalletModal()
|
||||
}
|
||||
}, [account, toggleWalletModal])
|
||||
|
||||
return (
|
||||
<PageWrapper gap="lg" justify="center">
|
||||
<RowBetween style={{ gap: '24px' }}>
|
||||
<ThemedText.DeprecatedMediumHeader style={{ margin: 0 }}>
|
||||
<Trans>
|
||||
{currencyA?.symbol}-{currencyB?.symbol} Liquidity Mining
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<DoubleCurrencyLogo currency0={currencyA ?? undefined} currency1={currencyB ?? undefined} size={24} />
|
||||
</RowBetween>
|
||||
|
||||
<DataRow style={{ gap: '24px' }}>
|
||||
<PoolData>
|
||||
<AutoColumn gap="sm">
|
||||
<ThemedText.DeprecatedBody style={{ margin: 0 }}>
|
||||
<Trans>Total deposits</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedBody fontSize={24} fontWeight={500}>
|
||||
{valueOfTotalStakedAmountInUSDC
|
||||
? `$${valueOfTotalStakedAmountInUSDC.toFixed(0, { groupSeparator: ',' })}`
|
||||
: `${valueOfTotalStakedAmountInWETH?.toSignificant(4, { groupSeparator: ',' }) ?? '-'} ETH`}
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</PoolData>
|
||||
<PoolData>
|
||||
<AutoColumn gap="sm">
|
||||
<ThemedText.DeprecatedBody style={{ margin: 0 }}>
|
||||
<Trans>Pool Rate</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedBody fontSize={24} fontWeight={500}>
|
||||
{stakingInfo?.active ? (
|
||||
<Trans>
|
||||
{stakingInfo.totalRewardRate?.multiply(BIG_INT_SECONDS_IN_WEEK)?.toFixed(0, { groupSeparator: ',' })}{' '}
|
||||
UNI / week
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>0 UNI / week</Trans>
|
||||
)}
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
</PoolData>
|
||||
</DataRow>
|
||||
|
||||
{showAddLiquidityButton && (
|
||||
<VoteCard>
|
||||
<CardBGImage />
|
||||
<CardNoise />
|
||||
<CardSection>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedWhite fontWeight={600}>
|
||||
<Trans>Step 1. Get UNI-V2 Liquidity tokens</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
<RowBetween style={{ marginBottom: '1rem' }}>
|
||||
<ThemedText.DeprecatedWhite fontSize={14}>
|
||||
<Trans>
|
||||
UNI-V2 LP tokens are required. Once you've added liquidity to the {currencyA?.symbol}-
|
||||
{currencyB?.symbol} pool you can stake your liquidity tokens on this page.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
$borderRadius="8px"
|
||||
width="fit-content"
|
||||
as={Link}
|
||||
to={`/add/${currencyA && currencyId(currencyA)}/${currencyB && currencyId(currencyB)}`}
|
||||
>
|
||||
<Trans>
|
||||
Add {currencyA?.symbol}-{currencyB?.symbol} liquidity
|
||||
</Trans>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
</CardSection>
|
||||
<CardBGImage />
|
||||
<CardNoise />
|
||||
</VoteCard>
|
||||
)}
|
||||
|
||||
{stakingInfo && (
|
||||
<>
|
||||
<StakingModal
|
||||
isOpen={showStakingModal}
|
||||
onDismiss={() => setShowStakingModal(false)}
|
||||
stakingInfo={stakingInfo}
|
||||
userLiquidityUnstaked={userLiquidityUnstaked}
|
||||
/>
|
||||
<UnstakingModal
|
||||
isOpen={showUnstakingModal}
|
||||
onDismiss={() => setShowUnstakingModal(false)}
|
||||
stakingInfo={stakingInfo}
|
||||
/>
|
||||
<ClaimRewardModal
|
||||
isOpen={showClaimRewardModal}
|
||||
onDismiss={() => setShowClaimRewardModal(false)}
|
||||
stakingInfo={stakingInfo}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PositionInfo gap="lg" justify="center" dim={showAddLiquidityButton}>
|
||||
<BottomSection gap="lg" justify="center">
|
||||
<StyledDataCard disabled={disableTop} bgColor={backgroundColor} showBackground={!showAddLiquidityButton}>
|
||||
<CardSection>
|
||||
<CardBGImage desaturate />
|
||||
<CardNoise />
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedWhite fontWeight={600}>
|
||||
<Trans>Your liquidity deposits</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
<RowBetween style={{ alignItems: 'baseline' }}>
|
||||
<ThemedText.DeprecatedWhite fontSize={36} fontWeight={600}>
|
||||
{stakingInfo?.stakedAmount?.toSignificant(6) ?? '-'}
|
||||
</ThemedText.DeprecatedWhite>
|
||||
<ThemedText.DeprecatedWhite>
|
||||
<Trans>
|
||||
UNI-V2 {currencyA?.symbol}-{currencyB?.symbol}
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</CardSection>
|
||||
</StyledDataCard>
|
||||
<StyledBottomCard dim={stakingInfo?.stakedAmount?.equalTo(JSBI.BigInt(0))}>
|
||||
<CardBGImage desaturate />
|
||||
<CardNoise />
|
||||
<AutoColumn gap="sm">
|
||||
<RowBetween>
|
||||
<div>
|
||||
<ThemedText.DeprecatedBlack>
|
||||
<Trans>Your unclaimed UNI</Trans>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</div>
|
||||
{stakingInfo?.earnedAmount && JSBI.notEqual(BIG_INT_ZERO, stakingInfo?.earnedAmount?.quotient) && (
|
||||
<ButtonEmpty
|
||||
padding="8px"
|
||||
$borderRadius="8px"
|
||||
width="fit-content"
|
||||
onClick={() => setShowClaimRewardModal(true)}
|
||||
>
|
||||
<Trans>Claim</Trans>
|
||||
</ButtonEmpty>
|
||||
)}
|
||||
</RowBetween>
|
||||
<RowBetween style={{ alignItems: 'baseline' }}>
|
||||
<ThemedText.DeprecatedLargeHeader fontSize={36} fontWeight={600}>
|
||||
<CountUp
|
||||
key={countUpAmount}
|
||||
isCounting
|
||||
decimalPlaces={4}
|
||||
start={parseFloat(countUpAmountPrevious)}
|
||||
end={parseFloat(countUpAmount)}
|
||||
thousandsSeparator=","
|
||||
duration={1}
|
||||
/>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
<ThemedText.DeprecatedBlack fontSize={16} fontWeight={500}>
|
||||
<span role="img" aria-label="wizard-icon" style={{ marginRight: '8px ' }}>
|
||||
⚡
|
||||
</span>
|
||||
|
||||
{stakingInfo?.active ? (
|
||||
<Trans>
|
||||
{stakingInfo.rewardRate?.multiply(BIG_INT_SECONDS_IN_WEEK)?.toFixed(0, { groupSeparator: ',' })}{' '}
|
||||
UNI / week
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>0 UNI / week</Trans>
|
||||
)}
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</StyledBottomCard>
|
||||
</BottomSection>
|
||||
<ThemedText.DeprecatedMain style={{ textAlign: 'center' }} fontSize={14}>
|
||||
<span role="img" aria-label="wizard-icon" style={{ marginRight: '8px' }}>
|
||||
⭐️
|
||||
</span>
|
||||
<Trans>When you withdraw, the contract will automagically claim UNI on your behalf!</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
|
||||
{!showAddLiquidityButton && (
|
||||
<DataRow style={{ marginBottom: '1rem' }}>
|
||||
{stakingInfo && stakingInfo.active && (
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px" width="160px" onClick={handleDepositClick}>
|
||||
{stakingInfo?.stakedAmount?.greaterThan(JSBI.BigInt(0)) ? (
|
||||
<Trans>Deposit</Trans>
|
||||
) : (
|
||||
<Trans>Deposit UNI-V2 LP Tokens</Trans>
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
|
||||
{stakingInfo?.stakedAmount?.greaterThan(JSBI.BigInt(0)) && (
|
||||
<>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
$borderRadius="8px"
|
||||
width="160px"
|
||||
onClick={() => setShowUnstakingModal(true)}
|
||||
>
|
||||
<Trans>Withdraw</Trans>
|
||||
</ButtonPrimary>
|
||||
</>
|
||||
)}
|
||||
</DataRow>
|
||||
)}
|
||||
{!userLiquidityUnstaked ? null : userLiquidityUnstaked.equalTo('0') ? null : !stakingInfo?.active ? null : (
|
||||
<ThemedText.DeprecatedMain>
|
||||
<Trans>{userLiquidityUnstaked.toSignificant(6)} UNI-V2 LP tokens available</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
)}
|
||||
</PositionInfo>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import JSBI from 'jsbi'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { OutlineCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import PoolCard from '../../components/earn/PoolCard'
|
||||
import { CardBGImage, CardNoise, CardSection, DataCard } from '../../components/earn/styled'
|
||||
import Loader from '../../components/Loader'
|
||||
import { RowBetween } from '../../components/Row'
|
||||
import { BIG_INT_ZERO } from '../../constants/misc'
|
||||
import { STAKING_REWARDS_INFO, useStakingInfo } from '../../state/stake/hooks'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { Countdown } from './Countdown'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
padding: 68px 8px 0px;
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||
padding: 48px 8px 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
padding-top: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const TopSection = styled(AutoColumn)`
|
||||
max-width: 720px;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const PoolSection = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
column-gap: 10px;
|
||||
row-gap: 15px;
|
||||
width: 100%;
|
||||
justify-self: center;
|
||||
`
|
||||
|
||||
const DataRow = styled(RowBetween)`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
flex-direction: column;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function Earn() {
|
||||
const theme = useTheme()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
// staking info for connected account
|
||||
const stakingInfos = useStakingInfo()
|
||||
|
||||
/**
|
||||
* only show staking cards with balance
|
||||
* @todo only account for this if rewards are inactive
|
||||
*/
|
||||
const stakingInfosWithBalance = stakingInfos?.filter((s) => JSBI.greaterThan(s.stakedAmount.quotient, BIG_INT_ZERO))
|
||||
|
||||
// toggle copy if rewards are inactive
|
||||
const stakingRewardsExist = Boolean(typeof chainId === 'number' && (STAKING_REWARDS_INFO[chainId]?.length ?? 0) > 0)
|
||||
|
||||
return (
|
||||
<PageWrapper gap="lg" justify="center">
|
||||
<TopSection gap="md">
|
||||
<DataCard>
|
||||
<CardBGImage />
|
||||
<CardNoise />
|
||||
<CardSection>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedWhite fontWeight={600}>
|
||||
<Trans>Uniswap liquidity mining</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedWhite fontSize={14}>
|
||||
<Trans>
|
||||
Deposit your Liquidity Provider tokens to receive UNI, the Uniswap protocol governance token.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</RowBetween>{' '}
|
||||
<ExternalLink
|
||||
style={{ color: theme.white, textDecoration: 'underline' }}
|
||||
href="https://uniswap.org/blog/uni/"
|
||||
target="_blank"
|
||||
>
|
||||
<ThemedText.DeprecatedWhite fontSize={14}>
|
||||
<Trans>Read more about UNI</Trans>
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</ExternalLink>
|
||||
</AutoColumn>
|
||||
</CardSection>
|
||||
<CardBGImage />
|
||||
<CardNoise />
|
||||
</DataCard>
|
||||
</TopSection>
|
||||
|
||||
<AutoColumn gap="lg" style={{ width: '100%', maxWidth: '720px' }}>
|
||||
<DataRow style={{ alignItems: 'baseline' }}>
|
||||
<ThemedText.DeprecatedMediumHeader style={{ marginTop: '0.5rem' }}>
|
||||
<Trans>Participating pools</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<Countdown exactEnd={stakingInfos?.[0]?.periodFinish} />
|
||||
</DataRow>
|
||||
|
||||
<PoolSection>
|
||||
{stakingRewardsExist && stakingInfos?.length === 0 ? (
|
||||
<Loader style={{ margin: 'auto' }} />
|
||||
) : !stakingRewardsExist ? (
|
||||
<OutlineCard>
|
||||
<Trans>No active pools</Trans>
|
||||
</OutlineCard>
|
||||
) : stakingInfos?.length !== 0 && stakingInfosWithBalance.length === 0 ? (
|
||||
<OutlineCard>
|
||||
<Trans>No active pools</Trans>
|
||||
</OutlineCard>
|
||||
) : (
|
||||
stakingInfosWithBalance?.map((stakingInfo) => {
|
||||
// need to sort by added liquidity here
|
||||
return <PoolCard key={stakingInfo.stakingRewardAddress} stakingInfo={stakingInfo} />
|
||||
})
|
||||
)}
|
||||
</PoolSection>
|
||||
</AutoColumn>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Trace, TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName, PageName } from '@uniswap/analytics-events'
|
||||
import { BaseButton } from 'components/Button'
|
||||
import { LandingRedirectVariant, useLandingRedirectFlag } from 'featureFlags/flags/landingRedirect'
|
||||
import Swap from 'pages/Swap'
|
||||
import { useEffect } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { parse } from 'qs'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Link as NativeLink } from 'react-router-dom'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
@@ -32,7 +35,7 @@ const Gradient = styled.div<{ isDarkMode: boolean }>`
|
||||
isDarkMode
|
||||
? 'linear-gradient(rgba(8, 10, 24, 0) 0%, rgb(8 10 24 / 100%) 45%)'
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0) 0%, rgb(255 255 255 /100%) 45%)'};
|
||||
z-index: ${Z_INDEX.dropdown};
|
||||
z-index: ${Z_INDEX.under_dropdown};
|
||||
pointer-events: none;
|
||||
`
|
||||
|
||||
@@ -55,7 +58,7 @@ const ContentContainer = styled.div<{ isDarkMode: boolean }>`
|
||||
max-width: min(720px, 90%);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: ${Z_INDEX.dropdown};
|
||||
z-index: ${Z_INDEX.under_dropdown};
|
||||
padding: 32px 0 80px;
|
||||
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} opacity`};
|
||||
|
||||
@@ -176,40 +179,65 @@ export default function Landing() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [showContent, setShowContent] = useState(false)
|
||||
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
||||
const landingRedirectFlag = useLandingRedirectFlag()
|
||||
const navigate = useNavigate()
|
||||
const queryParams = parse(location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
|
||||
// This can be simplified significantly once the flag is removed! For now being explicit is clearer.
|
||||
useEffect(() => {
|
||||
if (queryParams.intro) {
|
||||
setShowContent(true)
|
||||
} else if (selectedWallet) {
|
||||
if (landingRedirectFlag === LandingRedirectVariant.Enabled) {
|
||||
navigate('/swap')
|
||||
} else {
|
||||
setShowContent(true)
|
||||
}
|
||||
} else {
|
||||
setShowContent(true)
|
||||
}
|
||||
}, [navigate, selectedWallet, landingRedirectFlag, queryParams.intro])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<Trace page={PageName.LANDING_PAGE} shouldLogImpression>
|
||||
<PageContainer>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.ELEMENT_CLICKED}
|
||||
element={ElementName.LANDING_PAGE_SWAP_ELEMENT}
|
||||
>
|
||||
<Link to="/swap">
|
||||
<LandingSwap />
|
||||
</Link>
|
||||
</TraceEvent>
|
||||
<Glow />
|
||||
<Gradient isDarkMode={isDarkMode} />
|
||||
<ContentContainer isDarkMode={isDarkMode}>
|
||||
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText>
|
||||
<SubTextContainer>
|
||||
<SubText>Buy, sell, and explore tokens and NFTs</SubText>
|
||||
</SubTextContainer>
|
||||
<ActionsContainer>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.ELEMENT_CLICKED}
|
||||
element={ElementName.CONTINUE_BUTTON}
|
||||
>
|
||||
<ButtonCTA as={Link} to="/swap">
|
||||
<ButtonCTAText>Get started</ButtonCTAText>
|
||||
</ButtonCTA>
|
||||
</TraceEvent>
|
||||
</ActionsContainer>
|
||||
</ContentContainer>
|
||||
</PageContainer>
|
||||
{showContent && (
|
||||
<PageContainer>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.ELEMENT_CLICKED}
|
||||
element={ElementName.LANDING_PAGE_SWAP_ELEMENT}
|
||||
>
|
||||
<Link to="/swap">
|
||||
<LandingSwap />
|
||||
</Link>
|
||||
</TraceEvent>
|
||||
<Glow />
|
||||
<Gradient isDarkMode={isDarkMode} />
|
||||
<ContentContainer isDarkMode={isDarkMode}>
|
||||
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText>
|
||||
<SubTextContainer>
|
||||
<SubText>Buy, sell, and explore tokens and NFTs</SubText>
|
||||
</SubTextContainer>
|
||||
<ActionsContainer>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.ELEMENT_CLICKED}
|
||||
element={ElementName.CONTINUE_BUTTON}
|
||||
>
|
||||
<ButtonCTA as={Link} to="/swap">
|
||||
<ButtonCTAText>Get started</ButtonCTAText>
|
||||
</ButtonCTA>
|
||||
</TraceEvent>
|
||||
</ActionsContainer>
|
||||
</ContentContainer>
|
||||
</PageContainer>
|
||||
)}
|
||||
</Trace>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -441,7 +441,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
])
|
||||
|
||||
// errors
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
const [swapQuoteReceivedDate, setSwapQuoteReceivedDate] = useState<Date | undefined>()
|
||||
|
||||
// warnings on the greater of fiat value price impact and execution price impact
|
||||
@@ -663,8 +662,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
trade={trade}
|
||||
syncing={routeIsSyncing}
|
||||
loading={routeIsLoading}
|
||||
showInverted={showInverted}
|
||||
setShowInverted={setShowInverted}
|
||||
allowedSlippage={allowedSlippage}
|
||||
/>
|
||||
</DetailsSwapSection>
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Navigate, useLocation } from 'react-router-dom'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
import { ApplicationModal, setOpenModal } from '../../state/application/reducer'
|
||||
|
||||
// Redirects to swap but only replace the pathname
|
||||
export function RedirectPathToSwapOnly() {
|
||||
const location = useLocation()
|
||||
return <Navigate to={{ ...location, pathname: '/swap' }} replace />
|
||||
}
|
||||
|
||||
export function OpenClaimAddressModalAndRedirectToSwap() {
|
||||
const dispatch = useAppDispatch()
|
||||
useEffect(() => {
|
||||
dispatch(setOpenModal(ApplicationModal.ADDRESS_CLAIM))
|
||||
}, [dispatch])
|
||||
return <RedirectPathToSwapOnly />
|
||||
}
|
||||
|
||||
@@ -1,15 +1,87 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
|
||||
import { AppState } from '../index'
|
||||
import { addPopup, ApplicationModal, PopupContent, removePopup, setOpenModal } from './reducer'
|
||||
import {
|
||||
addPopup,
|
||||
ApplicationModal,
|
||||
PopupContent,
|
||||
removePopup,
|
||||
setFiatOnrampAvailability,
|
||||
setOpenModal,
|
||||
} from './reducer'
|
||||
|
||||
export function useModalIsOpen(modal: ApplicationModal): boolean {
|
||||
const openModal = useAppSelector((state: AppState) => state.application.openModal)
|
||||
return openModal === modal
|
||||
}
|
||||
|
||||
/** @ref https://dashboard.moonpay.com/api_reference/client_side_api#ip_addresses */
|
||||
interface MoonpayIPAddressesResponse {
|
||||
alpha3?: string
|
||||
isAllowed?: boolean
|
||||
isBuyAllowed?: boolean
|
||||
isSellAllowed?: boolean
|
||||
}
|
||||
|
||||
async function getMoonpayAvailability(): Promise<boolean> {
|
||||
const moonpayPublishableKey = process.env.REACT_APP_MOONPAY_PUBLISHABLE_KEY
|
||||
if (!moonpayPublishableKey) {
|
||||
throw new Error('Must provide a publishable key for moonpay.')
|
||||
}
|
||||
const moonpayApiURI = process.env.REACT_APP_MOONPAY_API
|
||||
if (!moonpayApiURI) {
|
||||
throw new Error('Must provide an api endpoint for moonpay.')
|
||||
}
|
||||
const res = await fetch(`${moonpayApiURI}/v4/ip_address?apiKey=${moonpayPublishableKey}`)
|
||||
const data = await (res.json() as Promise<MoonpayIPAddressesResponse>)
|
||||
return data.isBuyAllowed ?? false
|
||||
}
|
||||
|
||||
export function useFiatOnrampAvailability(shouldCheck: boolean, callback?: () => void) {
|
||||
const dispatch = useAppDispatch()
|
||||
const { available, availabilityChecked } = useAppSelector((state: AppState) => state.application.fiatOnramp)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
async function checkAvailability() {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await getMoonpayAvailability()
|
||||
sendAnalyticsEvent('MoonPay Geochecker', { success: result })
|
||||
if (stale) return
|
||||
dispatch(setFiatOnrampAvailability(result))
|
||||
if (result && callback) {
|
||||
callback()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error checking onramp availability', e.toString())
|
||||
if (stale) return
|
||||
setError('Error, try again later.')
|
||||
dispatch(setFiatOnrampAvailability(false))
|
||||
} finally {
|
||||
if (stale) return
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (!availabilityChecked && shouldCheck) {
|
||||
checkAvailability()
|
||||
}
|
||||
|
||||
let stale = false
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
}, [availabilityChecked, callback, dispatch, shouldCheck])
|
||||
|
||||
return { available, availabilityChecked, loading, error }
|
||||
}
|
||||
|
||||
export function useToggleModal(modal: ApplicationModal): () => void {
|
||||
const isOpen = useModalIsOpen(modal)
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -21,6 +93,11 @@ export function useCloseModal(_modal: ApplicationModal): () => void {
|
||||
return useCallback(() => dispatch(setOpenModal(null)), [dispatch])
|
||||
}
|
||||
|
||||
export function useOpenModal(modal: ApplicationModal): () => void {
|
||||
const dispatch = useAppDispatch()
|
||||
return useCallback(() => dispatch(setOpenModal(modal)), [dispatch, modal])
|
||||
}
|
||||
|
||||
export function useToggleWalletModal(): () => void {
|
||||
return useToggleModal(ApplicationModal.WALLET)
|
||||
}
|
||||
|
||||
@@ -15,36 +15,39 @@ export type PopupContent =
|
||||
|
||||
export enum ApplicationModal {
|
||||
ADDRESS_CLAIM,
|
||||
UNISWAP_NFT_AIRDROP_CLAIM,
|
||||
BLOCKED_ACCOUNT,
|
||||
DELEGATE,
|
||||
CLAIM_POPUP,
|
||||
DELEGATE,
|
||||
EXECUTE,
|
||||
FEATURE_FLAGS,
|
||||
FIAT_ONRAMP,
|
||||
MENU,
|
||||
NETWORK_FILTER,
|
||||
NETWORK_SELECTOR,
|
||||
POOL_OVERVIEW_OPTIONS,
|
||||
PRIVACY_POLICY,
|
||||
QUEUE,
|
||||
SELF_CLAIM,
|
||||
SETTINGS,
|
||||
SHARE,
|
||||
TIME_SELECTOR,
|
||||
VOTE,
|
||||
WALLET,
|
||||
WALLET_DROPDOWN,
|
||||
QUEUE,
|
||||
EXECUTE,
|
||||
TIME_SELECTOR,
|
||||
SHARE,
|
||||
NETWORK_FILTER,
|
||||
FEATURE_FLAGS,
|
||||
UNISWAP_NFT_AIRDROP_CLAIM,
|
||||
}
|
||||
|
||||
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
|
||||
|
||||
export interface ApplicationState {
|
||||
readonly chainId: number | null
|
||||
readonly fiatOnramp: { available: boolean; availabilityChecked: boolean }
|
||||
readonly openModal: ApplicationModal | null
|
||||
readonly popupList: PopupList
|
||||
}
|
||||
|
||||
const initialState: ApplicationState = {
|
||||
fiatOnramp: { available: false, availabilityChecked: false },
|
||||
chainId: null,
|
||||
openModal: null,
|
||||
popupList: [],
|
||||
@@ -54,6 +57,9 @@ const applicationSlice = createSlice({
|
||||
name: 'application',
|
||||
initialState,
|
||||
reducers: {
|
||||
setFiatOnrampAvailability(state, { payload: available }) {
|
||||
state.fiatOnramp = { available, availabilityChecked: true }
|
||||
},
|
||||
updateChainId(state, action) {
|
||||
const { chainId } = action.payload
|
||||
state.chainId = chainId
|
||||
@@ -81,5 +87,6 @@ const applicationSlice = createSlice({
|
||||
},
|
||||
})
|
||||
|
||||
export const { updateChainId, setOpenModal, addPopup, removePopup } = applicationSlice.actions
|
||||
export const { updateChainId, setFiatOnrampAvailability, setOpenModal, addPopup, removePopup } =
|
||||
applicationSlice.actions
|
||||
export default applicationSlice.reducer
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { supportedChainId } from 'utils/supportedChainId'
|
||||
|
||||
import { updateChainId } from './reducer'
|
||||
import { useCloseModal } from './hooks'
|
||||
import { ApplicationModal, updateChainId } from './reducer'
|
||||
|
||||
export default function Updater(): null {
|
||||
const { chainId, provider } = useWeb3React()
|
||||
const { account, chainId, provider } = useWeb3React()
|
||||
const dispatch = useAppDispatch()
|
||||
const windowVisible = useIsWindowVisible()
|
||||
|
||||
const [activeChainId, setActiveChainId] = useState(chainId)
|
||||
|
||||
const closeModal = useCloseModal(ApplicationModal.WALLET_DROPDOWN)
|
||||
const previousAccountValue = useRef(account)
|
||||
useEffect(() => {
|
||||
if (account && account !== previousAccountValue.current) {
|
||||
previousAccountValue.current = account
|
||||
closeModal()
|
||||
}
|
||||
}, [account, closeModal])
|
||||
|
||||
useEffect(() => {
|
||||
if (provider && chainId && windowVisible) {
|
||||
setActiveChainId(chainId)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
@@ -8,8 +7,7 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import JSBI from 'jsbi'
|
||||
import { NEVER_RELOAD, useMultipleContractSingleData } from 'lib/hooks/multicall'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { DAI, UNI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
|
||||
|
||||
@@ -17,9 +15,7 @@ const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI)
|
||||
|
||||
export const STAKING_GENESIS = 1600387200
|
||||
|
||||
export const REWARDS_DURATION_DAYS = 60
|
||||
|
||||
export const STAKING_REWARDS_INFO: {
|
||||
const STAKING_REWARDS_INFO: {
|
||||
[chainId: number]: {
|
||||
tokens: [Token, Token]
|
||||
stakingRewardAddress: string
|
||||
@@ -45,7 +41,7 @@ export const STAKING_REWARDS_INFO: {
|
||||
],
|
||||
}
|
||||
|
||||
export interface StakingInfo {
|
||||
interface StakingInfo {
|
||||
// the address of the reward contract
|
||||
stakingRewardAddress: string
|
||||
// the tokens involved in this pair
|
||||
@@ -227,35 +223,3 @@ export function useStakingInfo(pairToFilterBy?: Pair | null): StakingInfo[] {
|
||||
uni,
|
||||
])
|
||||
}
|
||||
|
||||
// based on typed value
|
||||
export function useDerivedStakeInfo(
|
||||
typedValue: string,
|
||||
stakingToken: Token | undefined,
|
||||
userLiquidityUnstaked: CurrencyAmount<Token> | undefined
|
||||
): {
|
||||
parsedAmount?: CurrencyAmount<Token>
|
||||
error?: ReactNode
|
||||
} {
|
||||
const { account } = useWeb3React()
|
||||
|
||||
const parsedInput: CurrencyAmount<Token> | undefined = tryParseCurrencyAmount(typedValue, stakingToken)
|
||||
|
||||
const parsedAmount =
|
||||
parsedInput && userLiquidityUnstaked && JSBI.lessThanOrEqual(parsedInput.quotient, userLiquidityUnstaked.quotient)
|
||||
? parsedInput
|
||||
: undefined
|
||||
|
||||
let error: ReactNode | undefined
|
||||
if (!account) {
|
||||
error = <Trans>Connect Wallet</Trans>
|
||||
}
|
||||
if (!parsedAmount) {
|
||||
error = error ?? <Trans>Enter an amount</Trans>
|
||||
}
|
||||
|
||||
return {
|
||||
parsedAmount,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,8 @@ import { AppState } from '../index'
|
||||
import {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
updateFiatOnrampAcknowledgments,
|
||||
updateHideClosedPositions,
|
||||
updateHideNFTWelcomeModal,
|
||||
updateShowNftPromoBanner,
|
||||
updateUserClientSideRouter,
|
||||
updateUserDarkMode,
|
||||
updateUserDeadline,
|
||||
@@ -105,13 +104,24 @@ export function useExpertModeManager(): [boolean, () => void] {
|
||||
return [expertMode, toggleSetExpertMode]
|
||||
}
|
||||
|
||||
export function useHideNFTWelcomeModal(): [boolean | undefined, () => void] {
|
||||
interface FiatOnrampAcknowledgements {
|
||||
renderCount: number
|
||||
system: boolean
|
||||
user: boolean
|
||||
}
|
||||
export function useFiatOnrampAck(): [
|
||||
FiatOnrampAcknowledgements,
|
||||
(acknowledgements: Partial<FiatOnrampAcknowledgements>) => void
|
||||
] {
|
||||
const dispatch = useAppDispatch()
|
||||
const hideNFTWelcomeModal = useAppSelector((state) => state.user.hideNFTWelcomeModal)
|
||||
const hideModal = useCallback(() => {
|
||||
dispatch(updateHideNFTWelcomeModal({ hideNFTWelcomeModal: true }))
|
||||
}, [dispatch])
|
||||
return [hideNFTWelcomeModal, hideModal]
|
||||
const fiatOnrampAcknowledgments = useAppSelector((state) => state.user.fiatOnrampAcknowledgments)
|
||||
const setAcknowledgements = useCallback(
|
||||
(acks: Partial<FiatOnrampAcknowledgements>) => {
|
||||
dispatch(updateFiatOnrampAcknowledgments(acks))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
return [fiatOnrampAcknowledgments, setAcknowledgements]
|
||||
}
|
||||
|
||||
export function useClientSideRouter(): [boolean, (userClientSideRouter: boolean) => void] {
|
||||
@@ -258,17 +268,6 @@ export function useURLWarningVisible(): boolean {
|
||||
return useAppSelector((state: AppState) => state.user.URLWarningVisible)
|
||||
}
|
||||
|
||||
export function useHideNftPromoBanner(): [boolean, () => void] {
|
||||
const dispatch = useAppDispatch()
|
||||
const hideNftPromoBanner = useAppSelector((state) => state.user.hideNFTPromoBanner)
|
||||
|
||||
const toggleHideNftPromoBanner = useCallback(() => {
|
||||
dispatch(updateShowNftPromoBanner({ hideNFTPromoBanner: true }))
|
||||
}, [dispatch])
|
||||
|
||||
return [hideNftPromoBanner, toggleHideNftPromoBanner]
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two tokens return the liquidity token that represents its liquidity shares
|
||||
* @param tokenA one of the two tokens
|
||||
|
||||
@@ -9,6 +9,8 @@ import { SerializedPair, SerializedToken } from './types'
|
||||
const currentTimestamp = () => new Date().getTime()
|
||||
|
||||
export interface UserState {
|
||||
fiatOnrampAcknowledgments: { renderCount: number; system: boolean; user: boolean }
|
||||
|
||||
selectedWallet?: ConnectionType
|
||||
|
||||
// the timestamp of the last updateVersion action
|
||||
@@ -61,6 +63,7 @@ function pairKey(token0Address: string, token1Address: string) {
|
||||
}
|
||||
|
||||
export const initialState: UserState = {
|
||||
fiatOnrampAcknowledgments: { renderCount: 0, system: false, user: false },
|
||||
selectedWallet: undefined,
|
||||
matchesDarkMode: false,
|
||||
userDarkMode: null,
|
||||
@@ -84,6 +87,12 @@ const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateFiatOnrampAcknowledgments(
|
||||
state,
|
||||
{ payload }: { payload: Partial<{ renderCount: number; user: boolean; system: boolean }> }
|
||||
) {
|
||||
state.fiatOnrampAcknowledgments = { ...state.fiatOnrampAcknowledgments, ...payload }
|
||||
},
|
||||
updateSelectedWallet(state, { payload: { wallet } }) {
|
||||
state.selectedWallet = wallet
|
||||
},
|
||||
@@ -117,12 +126,6 @@ const userSlice = createSlice({
|
||||
updateHideClosedPositions(state, action) {
|
||||
state.userHideClosedPositions = action.payload.userHideClosedPositions
|
||||
},
|
||||
updateHideNFTWelcomeModal(state, action) {
|
||||
state.hideNFTWelcomeModal = action.payload.hideNFTWelcomeModal
|
||||
},
|
||||
updateShowNftPromoBanner(state, action) {
|
||||
state.hideNFTPromoBanner = action.payload.hideNFTPromoBanner
|
||||
},
|
||||
addSerializedToken(state, { payload: { serializedToken } }) {
|
||||
if (!state.tokens) {
|
||||
state.tokens = {}
|
||||
@@ -181,18 +184,17 @@ const userSlice = createSlice({
|
||||
})
|
||||
|
||||
export const {
|
||||
updateSelectedWallet,
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
updateFiatOnrampAcknowledgments,
|
||||
updateSelectedWallet,
|
||||
updateHideClosedPositions,
|
||||
updateMatchesDarkMode,
|
||||
updateUserClientSideRouter,
|
||||
updateHideNFTWelcomeModal,
|
||||
updateUserDarkMode,
|
||||
updateUserDeadline,
|
||||
updateUserExpertMode,
|
||||
updateUserLocale,
|
||||
updateUserSlippageTolerance,
|
||||
updateShowNftPromoBanner,
|
||||
} = userSlice.actions
|
||||
export default userSlice.reducer
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
export enum Z_INDEX {
|
||||
deprecated_zero = 0,
|
||||
deprecated_content = 1,
|
||||
under_dropdown = 990,
|
||||
dropdown = 1000,
|
||||
sticky = 1020,
|
||||
fixed = 1030,
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@@ -4195,10 +4195,10 @@
|
||||
"@typescript-eslint/types" "4.33.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@uniswap/analytics-events@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-1.3.1.tgz#e1ad912001646268ea16f64622c683ca2a63002d"
|
||||
integrity sha512-EzYLBU123TpTCNPfa8cN7cI3Ap8D5Rn0771/bAtjSElZROR6YnGFNj0cjnaHwo8SIUr5Ema7JLoXbMVUyreFXQ==
|
||||
"@uniswap/analytics-events@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-1.4.0.tgz#63b0fa55d9d258e3b29d07f38da194f9fa841d1d"
|
||||
integrity sha512-KdY/8iRTbVirJN/ms8f68MfGM+zuCuz4E35Lw4m7xcQ03YAlE9j3Oa3UhsFSCialn8F6brmrAPsx8a8Ps6uoKw==
|
||||
|
||||
"@uniswap/analytics@1.2.0":
|
||||
version "1.2.0"
|
||||
@@ -4209,10 +4209,10 @@
|
||||
react "^18.2.0"
|
||||
react-dom "^18.2.0"
|
||||
|
||||
"@uniswap/conedison@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.1.0.tgz#462a1e7dcfc70a653e765c145655faa207fd53f7"
|
||||
integrity sha512-mHnYkvy+xKfWDzWsqzgWKFl/V8C/KmSrj/2PCgT1R2ASxPzMCU/wzTW29HtIxf1cJ+A6sPwH2HqDZsbnNhKqNQ==
|
||||
"@uniswap/conedison@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.1.1.tgz#affec246613d1f52da3cdd0571ef8195b7b54d17"
|
||||
integrity sha512-xFHAcWRrU+/+/BInXy6SRiiNwUG0vxLWsoYgod66wWifUvnjfpItzlvJHUer1OOpLDsz0CL5Fb70vFJOGAGi8w==
|
||||
|
||||
"@uniswap/default-token-list@^2.0.0":
|
||||
version "2.2.0"
|
||||
|
||||
Reference in New Issue
Block a user