Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4529e3cc88 | ||
|
|
4d47470f33 | ||
|
|
aedc020646 | ||
|
|
fd8085722e | ||
|
|
a60a85db54 | ||
|
|
ad2472eac6 | ||
|
|
f4d4acacae | ||
|
|
a5d7af192c | ||
|
|
21a2863ae3 | ||
|
|
1f871d4e73 | ||
|
|
3690936aff |
@@ -3,8 +3,8 @@ describe('Redirect', () => {
|
|||||||
cy.visit('/create-proposal')
|
cy.visit('/create-proposal')
|
||||||
cy.url().should('match', /\/vote\/create-proposal/)
|
cy.url().should('match', /\/vote\/create-proposal/)
|
||||||
})
|
})
|
||||||
it('should redirect to /swap when visiting nonexist url', () => {
|
it('should redirect to /not-found when visiting nonexist url', () => {
|
||||||
cy.visit('/none-exist-url')
|
cy.visit('/none-exist-url')
|
||||||
cy.url().should('match', /\/swap/)
|
cy.url().should('match', /\/not-found/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
"@uniswap/v3-core": "1.0.0",
|
"@uniswap/v3-core": "1.0.0",
|
||||||
"@uniswap/v3-periphery": "^1.1.1",
|
"@uniswap/v3-periphery": "^1.1.1",
|
||||||
"@uniswap/v3-sdk": "^3.9.0",
|
"@uniswap/v3-sdk": "^3.9.0",
|
||||||
"@uniswap/widgets": "2.22.10",
|
"@uniswap/widgets": "2.22.11",
|
||||||
"@vanilla-extract/css": "^1.7.2",
|
"@vanilla-extract/css": "^1.7.2",
|
||||||
"@vanilla-extract/css-utils": "^0.1.2",
|
"@vanilla-extract/css-utils": "^0.1.2",
|
||||||
"@vanilla-extract/dynamic": "^2.0.2",
|
"@vanilla-extract/dynamic": "^2.0.2",
|
||||||
|
|||||||
BIN
src/assets/images/404-page-dark.png
Normal file
BIN
src/assets/images/404-page-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
src/assets/images/404-page-light.png
Normal file
BIN
src/assets/images/404-page-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
@@ -79,6 +79,13 @@ export const ButtonPrimary = styled(BaseButton)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const SmallButtonPrimary = styled(ButtonPrimary)`
|
||||||
|
width: auto;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
`
|
||||||
|
|
||||||
export const ButtonLight = styled(BaseButton)`
|
export const ButtonLight = styled(BaseButton)`
|
||||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||||
color: ${({ theme }) => theme.accentAction};
|
color: ${({ theme }) => theme.accentAction};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Group } from '@visx/group'
|
import { Group } from '@visx/group'
|
||||||
import { LinePath } from '@visx/shape'
|
import { LinePath } from '@visx/shape'
|
||||||
import { easeCubicInOut } from 'd3'
|
import { easeSinOut } from 'd3'
|
||||||
|
import ms from 'ms.macro'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { animated, useSpring } from 'react-spring'
|
import { animated, useSpring } from 'react-spring'
|
||||||
@@ -11,8 +12,8 @@ import { LineChartProps } from './LineChart'
|
|||||||
type AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
|
type AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
duration: 800,
|
duration: ms`0.8s`,
|
||||||
easing: easeCubicInOut,
|
easing: easeSinOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
// code reference: https://airbnb.io/visx/lineradial
|
// code reference: https://airbnb.io/visx/lineradial
|
||||||
@@ -91,4 +92,4 @@ function AnimatedInLineChart<T>({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AnimatedInLineChart
|
export default React.memo(AnimatedInLineChart) as typeof AnimatedInLineChart
|
||||||
|
|||||||
86
src/components/Charts/FadeInLineChart.tsx
Normal file
86
src/components/Charts/FadeInLineChart.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { Group } from '@visx/group'
|
||||||
|
import { LinePath } from '@visx/shape'
|
||||||
|
import { easeCubicInOut } from 'd3'
|
||||||
|
import React from 'react'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { animated, useSpring } from 'react-spring'
|
||||||
|
import { useTheme } from 'styled-components/macro'
|
||||||
|
|
||||||
|
import { LineChartProps } from './LineChart'
|
||||||
|
|
||||||
|
type FadedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'> & { dashed?: boolean }
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
duration: 3000,
|
||||||
|
easing: easeCubicInOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
// code reference: https://airbnb.io/visx/lineradial
|
||||||
|
|
||||||
|
function FadedInLineChart<T>({
|
||||||
|
data,
|
||||||
|
getX,
|
||||||
|
getY,
|
||||||
|
marginTop,
|
||||||
|
curve,
|
||||||
|
color,
|
||||||
|
strokeWidth,
|
||||||
|
dashed,
|
||||||
|
}: FadedInLineChartProps<T>) {
|
||||||
|
const lineRef = useRef<SVGPathElement>(null)
|
||||||
|
const [lineLength, setLineLength] = useState(0)
|
||||||
|
const [shouldAnimate, setShouldAnimate] = useState(false)
|
||||||
|
const [hasAnimatedIn, setHasAnimatedIn] = useState(false)
|
||||||
|
|
||||||
|
const spring = useSpring({
|
||||||
|
frame: shouldAnimate ? 0 : 1,
|
||||||
|
config,
|
||||||
|
onRest: () => {
|
||||||
|
setShouldAnimate(false)
|
||||||
|
setHasAnimatedIn(true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// We need to check to see after the "invisble" line has been drawn
|
||||||
|
// what the length is to be able to animate in the line for the first time
|
||||||
|
// This will run on each render to see if there is a new line length
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
useEffect(() => {
|
||||||
|
if (lineRef.current) {
|
||||||
|
const length = lineRef.current.getTotalLength()
|
||||||
|
if (length !== lineLength) {
|
||||||
|
setLineLength(length)
|
||||||
|
}
|
||||||
|
if (length > 0 && !shouldAnimate && !hasAnimatedIn) {
|
||||||
|
setShouldAnimate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const theme = useTheme()
|
||||||
|
const lineColor = color ?? theme.accentAction
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group top={marginTop}>
|
||||||
|
<LinePath curve={curve} x={getX} y={getY}>
|
||||||
|
{({ path }) => {
|
||||||
|
const d = path(data) || ''
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<animated.path
|
||||||
|
d={d}
|
||||||
|
ref={lineRef}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeOpacity={hasAnimatedIn ? 1 : spring.frame.to((v) => 1 - v)}
|
||||||
|
fill="none"
|
||||||
|
stroke={lineColor}
|
||||||
|
strokeDasharray={dashed ? '4,4' : undefined}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</LinePath>
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(FadedInLineChart) as typeof FadedInLineChart
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
import { ButtonLight, ButtonPrimary } from 'components/Button'
|
import { ButtonLight, SmallButtonPrimary } from 'components/Button'
|
||||||
import { ChevronUpIcon } from 'nft/components/icons'
|
import { ChevronUpIcon } from 'nft/components/icons'
|
||||||
import { useIsMobile } from 'nft/hooks'
|
import { useIsMobile } from 'nft/hooks'
|
||||||
import React, { PropsWithChildren, useState } from 'react'
|
import React, { PropsWithChildren, useState } from 'react'
|
||||||
@@ -23,13 +23,6 @@ const BodyWrapper = styled.div<{ margin?: string }>`
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SmallButtonPrimary = styled(ButtonPrimary)`
|
|
||||||
width: auto;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-radius: 12px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const SmallButtonLight = styled(ButtonLight)`
|
const SmallButtonLight = styled(ButtonLight)`
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||||
import { LandingPageVariant, useLandingPageFlag } from 'featureFlags/flags/landingPage'
|
|
||||||
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
|
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
|
||||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||||
@@ -203,12 +202,6 @@ export default function FeatureFlagModal() {
|
|||||||
<X size={24} />
|
<X size={24} />
|
||||||
</CloseButton>
|
</CloseButton>
|
||||||
</Header>
|
</Header>
|
||||||
<FeatureFlagOption
|
|
||||||
variant={LandingPageVariant}
|
|
||||||
value={useLandingPageFlag()}
|
|
||||||
featureFlag={FeatureFlag.landingPage}
|
|
||||||
label="Landing page"
|
|
||||||
/>
|
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
variant={Permit2Variant}
|
variant={Permit2Variant}
|
||||||
value={usePermit2Flag()}
|
value={usePermit2Flag()}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { t } from '@lingui/macro'
|
import { t, Trans } from '@lingui/macro'
|
||||||
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
|
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
|
||||||
import { BrowserEvent, ElementName, EventName, SectionName } from '@uniswap/analytics-events'
|
import { BrowserEvent, ElementName, EventName, SectionName } from '@uniswap/analytics-events'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@@ -156,9 +156,9 @@ export const SearchBar = () => {
|
|||||||
>
|
>
|
||||||
<Row
|
<Row
|
||||||
className={clsx(
|
className={clsx(
|
||||||
` ${styles.nftSearchBar} ${!isOpen && !isMobile && magicalGradientOnHover} ${
|
styles.nftSearchBar,
|
||||||
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
|
!isOpen && !isMobile && magicalGradientOnHover,
|
||||||
} `
|
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
|
||||||
)}
|
)}
|
||||||
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
|
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
|
||||||
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
||||||
@@ -182,18 +182,23 @@ export const SearchBar = () => {
|
|||||||
element={ElementName.NAVBAR_SEARCH_INPUT}
|
element={ElementName.NAVBAR_SEARCH_INPUT}
|
||||||
properties={{ ...trace }}
|
properties={{ ...trace }}
|
||||||
>
|
>
|
||||||
<Box
|
<Trans
|
||||||
as="input"
|
id={placeholderText}
|
||||||
placeholder={placeholderText}
|
render={({ translation }) => (
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
<Box
|
||||||
!isOpen && toggleOpen()
|
as="input"
|
||||||
setSearchValue(event.target.value)
|
placeholder={translation as string}
|
||||||
}}
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
|
!isOpen && toggleOpen()
|
||||||
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
|
setSearchValue(event.target.value)
|
||||||
value={searchValue}
|
}}
|
||||||
ref={inputRef}
|
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
|
||||||
width="full"
|
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
|
||||||
|
value={searchValue}
|
||||||
|
ref={inputRef}
|
||||||
|
width="full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</TraceEvent>
|
</TraceEvent>
|
||||||
{!isOpen && <KeyShortCut>/</KeyShortCut>}
|
{!isOpen && <KeyShortCut>/</KeyShortCut>}
|
||||||
|
|||||||
@@ -9,14 +9,12 @@ import { AutoColumn } from '../Column'
|
|||||||
import ClaimPopup from './ClaimPopup'
|
import ClaimPopup from './ClaimPopup'
|
||||||
import PopupItem from './PopupItem'
|
import PopupItem from './PopupItem'
|
||||||
|
|
||||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
const MobilePopupWrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: ${({ height }) => height};
|
margin: 0 auto;
|
||||||
margin: ${({ height }) => (height ? '0 auto;' : 0)};
|
|
||||||
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
|
|
||||||
|
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||||
display: block;
|
display: block;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
@@ -74,16 +72,18 @@ export default function Popups() {
|
|||||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||||
))}
|
))}
|
||||||
</FixedPopupColumn>
|
</FixedPopupColumn>
|
||||||
<MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}>
|
{activePopups?.length > 0 && (
|
||||||
<MobilePopupInner>
|
<MobilePopupWrapper>
|
||||||
{activePopups // reverse so new items up front
|
<MobilePopupInner>
|
||||||
.slice(0)
|
{activePopups // reverse so new items up front
|
||||||
.reverse()
|
.slice(0)
|
||||||
.map((item) => (
|
.reverse()
|
||||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
.map((item) => (
|
||||||
))}
|
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||||
</MobilePopupInner>
|
))}
|
||||||
</MobilePopupWrapper>
|
</MobilePopupInner>
|
||||||
|
</MobilePopupWrapper>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const MobileWrapper = styled(AutoColumn)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const BaseWrapper = styled.div<{ disable?: boolean }>`
|
const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||||
border: 1px solid ${({ theme, disable }) => (disable ? theme.accentAction : theme.backgroundOutline)};
|
border: 1px solid ${({ theme, disable }) => (disable ? theme.accentActive : theme.backgroundOutline)};
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
@@ -30,8 +30,8 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
|
|||||||
background-color: ${({ theme }) => theme.hoverDefault};
|
background-color: ${({ theme }) => theme.hoverDefault};
|
||||||
}
|
}
|
||||||
|
|
||||||
color: ${({ theme, disable }) => disable && theme.accentAction};
|
color: ${({ theme, disable }) => disable && theme.accentActive};
|
||||||
background-color: ${({ theme, disable }) => disable && theme.accentActionSoft};
|
background-color: ${({ theme, disable }) => disable && theme.accentActiveSoft};
|
||||||
`
|
`
|
||||||
|
|
||||||
const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({
|
const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const BreadcrumbNavLink = styled(Link)`
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
transition-duration: ${({ theme }) => theme.transition.duration.fast};
|
transition-duration: ${({ theme }) => theme.transition.duration.fast};
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${({ theme }) => theme.textTertiary};
|
color: ${({ theme }) => theme.textTertiary};
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
|
|||||||
import { isPricePoint, PricePoint } from 'graphql/data/util'
|
import { isPricePoint, PricePoint } from 'graphql/data/util'
|
||||||
import { TimePeriod } from 'graphql/data/util'
|
import { TimePeriod } from 'graphql/data/util'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import { startTransition, Suspense, useMemo, useState } from 'react'
|
import { pageTimePeriodAtom } from 'pages/TokenDetails'
|
||||||
|
import { startTransition, Suspense, useMemo } from 'react'
|
||||||
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
||||||
|
|
||||||
import { filterTimeAtom } from '../state'
|
import { PriceChart } from './PriceChart'
|
||||||
import PriceChart from './PriceChart'
|
|
||||||
import TimePeriodSelector from './TimeSelector'
|
import TimePeriodSelector from './TimeSelector'
|
||||||
|
|
||||||
function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPriceQuery>): PricePoint[] | undefined {
|
function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPriceQuery>): PricePoint[] | undefined {
|
||||||
@@ -58,7 +58,7 @@ function Chart({
|
|||||||
}) {
|
}) {
|
||||||
const prices = usePreloadedTokenPriceQuery(priceQueryReference)
|
const prices = usePreloadedTokenPriceQuery(priceQueryReference)
|
||||||
// Initializes time period to global & maintain separate time period for subsequent changes
|
// Initializes time period to global & maintain separate time period for subsequent changes
|
||||||
const [timePeriod, setTimePeriod] = useState(useAtomValue(filterTimeAtom))
|
const timePeriod = useAtomValue(pageTimePeriodAtom)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartContainer>
|
<ChartContainer>
|
||||||
@@ -69,7 +69,6 @@ function Chart({
|
|||||||
currentTimePeriod={timePeriod}
|
currentTimePeriod={timePeriod}
|
||||||
onTimeChange={(t: TimePeriod) => {
|
onTimeChange={(t: TimePeriod) => {
|
||||||
startTransition(() => refetchTokenPrices(t))
|
startTransition(() => refetchTokenPrices(t))
|
||||||
setTimePeriod(t)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { formatUSDPrice } from '@uniswap/conedison/format'
|
|
||||||
import { AxisBottom, TickFormatter } from '@visx/axis'
|
import { AxisBottom, TickFormatter } from '@visx/axis'
|
||||||
import { localPoint } from '@visx/event'
|
import { localPoint } from '@visx/event'
|
||||||
import { EventType } from '@visx/event/lib/types'
|
import { EventType } from '@visx/event/lib/types'
|
||||||
import { GlyphCircle } from '@visx/glyph'
|
import { GlyphCircle } from '@visx/glyph'
|
||||||
import { Line } from '@visx/shape'
|
import { Line } from '@visx/shape'
|
||||||
import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
|
import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
|
||||||
|
import FadedInLineChart from 'components/Charts/FadeInLineChart'
|
||||||
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
|
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
|
||||||
import { PricePoint } from 'graphql/data/util'
|
import { PricePoint } from 'graphql/data/util'
|
||||||
import { TimePeriod } from 'graphql/data/util'
|
import { TimePeriod } from 'graphql/data/util'
|
||||||
@@ -13,6 +13,8 @@ import { useActiveLocale } from 'hooks/useActiveLocale'
|
|||||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { ArrowDownRight, ArrowUpRight, TrendingUp } from 'react-feather'
|
import { ArrowDownRight, ArrowUpRight, TrendingUp } from 'react-feather'
|
||||||
import styled, { useTheme } from 'styled-components/macro'
|
import styled, { useTheme } from 'styled-components/macro'
|
||||||
|
import { ThemedText } from 'theme'
|
||||||
|
import { textFadeIn } from 'theme/styles'
|
||||||
import {
|
import {
|
||||||
dayHourFormatter,
|
dayHourFormatter,
|
||||||
hourFormatter,
|
hourFormatter,
|
||||||
@@ -21,6 +23,7 @@ import {
|
|||||||
monthYearDayFormatter,
|
monthYearDayFormatter,
|
||||||
weekFormatter,
|
weekFormatter,
|
||||||
} from 'utils/formatChartTimes'
|
} from 'utils/formatChartTimes'
|
||||||
|
import { formatDollar } from 'utils/formatNumbers'
|
||||||
|
|
||||||
const DATA_EMPTY = { value: 0, timestamp: 0 }
|
const DATA_EMPTY = { value: 0, timestamp: 0 }
|
||||||
|
|
||||||
@@ -68,11 +71,19 @@ export const DeltaText = styled.span<{ delta: number | undefined }>`
|
|||||||
|
|
||||||
const ChartHeader = styled.div`
|
const ChartHeader = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
${textFadeIn};
|
||||||
|
animation-duration: ${({ theme }) => theme.transition.duration.medium};
|
||||||
`
|
`
|
||||||
export const TokenPrice = styled.span`
|
export const TokenPrice = styled.span`
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
line-height: 44px;
|
line-height: 44px;
|
||||||
`
|
`
|
||||||
|
const MissingPrice = styled(TokenPrice)`
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 44px;
|
||||||
|
color: ${({ theme }) => theme.textTertiary};
|
||||||
|
`
|
||||||
|
|
||||||
const DeltaContainer = styled.div`
|
const DeltaContainer = styled.div`
|
||||||
height: 16px;
|
height: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -84,6 +95,29 @@ export const ArrowCell = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
function fixChart(prices: PricePoint[] | undefined | null) {
|
||||||
|
if (!prices) return { prices: null, blanks: [] }
|
||||||
|
|
||||||
|
const fixedChart: PricePoint[] = []
|
||||||
|
const blanks: PricePoint[][] = []
|
||||||
|
let lastValue: PricePoint | undefined = undefined
|
||||||
|
for (let i = 0; i < prices.length; i++) {
|
||||||
|
if (prices[i].value !== 0) {
|
||||||
|
if (fixedChart.length === 0 && i !== 0) {
|
||||||
|
blanks.push([{ ...prices[0], value: prices[i].value }, prices[i]])
|
||||||
|
}
|
||||||
|
lastValue = prices[i]
|
||||||
|
fixedChart.push(prices[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastValue && lastValue !== prices[prices.length - 1]) {
|
||||||
|
blanks.push([lastValue, { ...prices[prices.length - 1], value: lastValue.value }])
|
||||||
|
}
|
||||||
|
|
||||||
|
return { prices: fixedChart, blanks }
|
||||||
|
}
|
||||||
|
|
||||||
const margin = { top: 100, bottom: 48, crosshair: 72 }
|
const margin = { top: 100, bottom: 48, crosshair: 72 }
|
||||||
const timeOptionsHeight = 44
|
const timeOptionsHeight = 44
|
||||||
|
|
||||||
@@ -94,24 +128,30 @@ interface PriceChartProps {
|
|||||||
timePeriod: TimePeriod
|
timePeriod: TimePeriod
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDisplayPrice(value: number) {
|
export function PriceChart({ width, height, prices: originalPrices, timePeriod }: PriceChartProps) {
|
||||||
const str = value.toFixed(9)
|
|
||||||
const [digits, decimals] = str.split('.')
|
|
||||||
// Displays longer string for numbers < $2 to show changes in both stablecoins & small values
|
|
||||||
if (digits === '0' || digits === '1')
|
|
||||||
return `$${digits + '.' + decimals.substring(0, 2) + decimals.substring(2).replace(/0+$/, '')}`
|
|
||||||
|
|
||||||
return formatUSDPrice(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
|
|
||||||
const locale = useActiveLocale()
|
const locale = useActiveLocale()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
|
const { prices, blanks } = useMemo(
|
||||||
|
() => (originalPrices && originalPrices.length > 0 ? fixChart(originalPrices) : { prices: null, blanks: [] }),
|
||||||
|
[originalPrices]
|
||||||
|
)
|
||||||
|
|
||||||
|
const chartAvailable = !!prices && prices.length > 0
|
||||||
|
const missingPricesMessage = !chartAvailable ? (
|
||||||
|
prices?.length === 0 ? (
|
||||||
|
<>
|
||||||
|
<Trans>Missing price data due to recently low trading volume on Uniswap v3</Trans>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Trans>Missing chart data</Trans>
|
||||||
|
)
|
||||||
|
) : null
|
||||||
|
|
||||||
// first price point on the x-axis of the current time period's chart
|
// first price point on the x-axis of the current time period's chart
|
||||||
const startingPrice = prices?.[0] ?? DATA_EMPTY
|
const startingPrice = originalPrices?.[0] ?? DATA_EMPTY
|
||||||
// last price point on the x-axis of the current time period's chart
|
// last price point on the x-axis of the current time period's chart
|
||||||
const endingPrice = prices?.[prices.length - 1] ?? DATA_EMPTY
|
const endingPrice = originalPrices?.[originalPrices.length - 1] ?? DATA_EMPTY
|
||||||
const [displayPrice, setDisplayPrice] = useState(startingPrice)
|
const [displayPrice, setDisplayPrice] = useState(startingPrice)
|
||||||
|
|
||||||
// set display price to ending price when prices have changed.
|
// set display price to ending price when prices have changed.
|
||||||
@@ -133,9 +173,9 @@ function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
|
|||||||
const rdScale = useMemo(
|
const rdScale = useMemo(
|
||||||
() =>
|
() =>
|
||||||
scaleLinear()
|
scaleLinear()
|
||||||
.domain(getPriceBounds(prices ?? []))
|
.domain(getPriceBounds(originalPrices ?? []))
|
||||||
.range([graphInnerHeight, 0]),
|
.range([graphInnerHeight, 0]),
|
||||||
[prices, graphInnerHeight]
|
[originalPrices, graphInnerHeight]
|
||||||
)
|
)
|
||||||
|
|
||||||
function tickFormat(
|
function tickFormat(
|
||||||
@@ -221,7 +261,6 @@ function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
|
|||||||
const arrow = getDeltaArrow(delta)
|
const arrow = getDeltaArrow(delta)
|
||||||
const crosshairEdgeMax = width * 0.85
|
const crosshairEdgeMax = width * 0.85
|
||||||
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
|
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
|
||||||
const hasData = prices && prices.length > 0
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default curve doesn't look good for the HOUR chart.
|
* Default curve doesn't look good for the HOUR chart.
|
||||||
@@ -233,27 +272,27 @@ function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
|
|||||||
const getX = useMemo(() => (p: PricePoint) => timeScale(p.timestamp), [timeScale])
|
const getX = useMemo(() => (p: PricePoint) => timeScale(p.timestamp), [timeScale])
|
||||||
const getY = useMemo(() => (p: PricePoint) => rdScale(p.value), [rdScale])
|
const getY = useMemo(() => (p: PricePoint) => rdScale(p.value), [rdScale])
|
||||||
const curve = useMemo(() => curveCardinal.tension(curveTension), [curveTension])
|
const curve = useMemo(() => curveCardinal.tension(curveTension), [curveTension])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChartHeader>
|
<ChartHeader>
|
||||||
<TokenPrice>{formatDisplayPrice(displayPrice.value)}</TokenPrice>
|
{displayPrice.value ? (
|
||||||
<DeltaContainer>
|
<>
|
||||||
<ArrowCell>{arrow}</ArrowCell>
|
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
|
||||||
<DeltaText delta={delta}>{formattedDelta}</DeltaText>
|
<DeltaContainer>
|
||||||
</DeltaContainer>
|
{formattedDelta}
|
||||||
|
<ArrowCell>{arrow}</ArrowCell>
|
||||||
|
</DeltaContainer>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<MissingPrice>Price Unavailable</MissingPrice>
|
||||||
|
<ThemedText.Caption style={{ color: theme.textTertiary }}>{missingPricesMessage}</ThemedText.Caption>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ChartHeader>
|
</ChartHeader>
|
||||||
{!hasData ? (
|
{!chartAvailable ? (
|
||||||
<MissingPriceChart
|
<MissingPriceChart width={width} height={graphHeight} message={!!displayPrice.value && missingPricesMessage} />
|
||||||
width={width}
|
|
||||||
height={graphHeight}
|
|
||||||
message={
|
|
||||||
prices?.length === 0 ? (
|
|
||||||
<Trans>This token doesn't have chart data because it hasn't been traded on Uniswap v3</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans>Missing chart data</Trans>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<svg width={width} height={graphHeight} style={{ minWidth: '100%' }}>
|
<svg width={width} height={graphHeight} style={{ minWidth: '100%' }}>
|
||||||
<AnimatedInLineChart
|
<AnimatedInLineChart
|
||||||
@@ -264,6 +303,19 @@ function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
|
|||||||
curve={curve}
|
curve={curve}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
/>
|
/>
|
||||||
|
{blanks.map((blank, index) => (
|
||||||
|
<FadedInLineChart
|
||||||
|
key={index}
|
||||||
|
data={blank}
|
||||||
|
getX={getX}
|
||||||
|
getY={getY}
|
||||||
|
marginTop={margin.top}
|
||||||
|
curve={curve}
|
||||||
|
strokeWidth={2}
|
||||||
|
color={theme.textTertiary}
|
||||||
|
dashed
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{crosshair !== null ? (
|
{crosshair !== null ? (
|
||||||
<g>
|
<g>
|
||||||
<AxisBottom
|
<AxisBottom
|
||||||
@@ -354,9 +406,7 @@ const StyledMissingChart = styled.svg`
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const chartBottomPadding = 15
|
const chartBottomPadding = 15
|
||||||
|
|
||||||
function MissingPriceChart({ width, height, message }: { width: number; height: number; message: ReactNode }) {
|
function MissingPriceChart({ width, height, message }: { width: number; height: number; message: ReactNode }) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const midPoint = height / 2 + 45
|
const midPoint = height / 2 + 45
|
||||||
@@ -369,18 +419,10 @@ function MissingPriceChart({ width, height, message }: { width: number; height:
|
|||||||
fill="transparent"
|
fill="transparent"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<TrendingUp stroke={theme.textTertiary} x={0} size={12} y={height - chartBottomPadding - 10} />
|
{message && <TrendingUp stroke={theme.textTertiary} x={0} size={12} y={height - chartBottomPadding - 10} />}
|
||||||
<text y={height - chartBottomPadding} x="20" fill={theme.textTertiary}>
|
<text y={height - chartBottomPadding} x="20" fill={theme.textTertiary}>
|
||||||
{message || <Trans>Missing chart data</Trans>}
|
{message}
|
||||||
</text>
|
</text>
|
||||||
<path
|
|
||||||
d={`M 0 ${height - 1}, ${width} ${height - 1}`}
|
|
||||||
stroke={theme.backgroundOutline}
|
|
||||||
fill="transparent"
|
|
||||||
strokeWidth="1"
|
|
||||||
/>
|
|
||||||
</StyledMissingChart>
|
</StyledMissingChart>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PriceChart
|
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ export const TokenInfoContainer = styled.div`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
${textFadeIn};
|
||||||
|
animation-duration: ${({ theme }) => theme.transition.duration.medium};
|
||||||
`
|
`
|
||||||
export const TokenNameCell = styled.div`
|
export const TokenNameCell = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -79,7 +81,6 @@ export const TokenNameCell = styled.div`
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
${textFadeIn}
|
|
||||||
`
|
`
|
||||||
/* Loading state bubbles */
|
/* Loading state bubbles */
|
||||||
const DetailBubble = styled(LoadingBubble)`
|
const DetailBubble = styled(LoadingBubble)`
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const PageWrapper = styled.div`
|
|||||||
padding: 68px 8px 0px;
|
padding: 68px 8px 0px;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||||
padding-top: 48px;
|
padding-top: 48px;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
traceJsonRpc = 'traceJsonRpc',
|
traceJsonRpc = 'traceJsonRpc',
|
||||||
landingPage = 'landingPage',
|
|
||||||
permit2 = 'permit2',
|
permit2 = 'permit2',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { BaseVariant } from '../index'
|
|
||||||
|
|
||||||
export function useLandingPageFlag(): BaseVariant {
|
|
||||||
return BaseVariant.Enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
export { BaseVariant as LandingPageVariant }
|
|
||||||
@@ -55,12 +55,13 @@ export enum BaseVariant {
|
|||||||
Enabled = 'enabled',
|
Enabled = 'enabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBaseFlag(flag: string): BaseVariant {
|
export function useBaseFlag(flag: string, defaultValue = BaseVariant.Control): BaseVariant {
|
||||||
switch (useFeatureFlagsContext().flags[flag]) {
|
switch (useFeatureFlagsContext().flags[flag]) {
|
||||||
case 'enabled':
|
case 'enabled':
|
||||||
return BaseVariant.Enabled
|
return BaseVariant.Enabled
|
||||||
case 'control':
|
case 'control':
|
||||||
default:
|
|
||||||
return BaseVariant.Control
|
return BaseVariant.Control
|
||||||
|
default:
|
||||||
|
return defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ enum SyncState {
|
|||||||
export enum PermitState {
|
export enum PermitState {
|
||||||
INVALID,
|
INVALID,
|
||||||
LOADING,
|
LOADING,
|
||||||
PERMIT_NEEDED,
|
APPROVAL_OR_PERMIT_NEEDED,
|
||||||
PERMITTED,
|
APPROVAL_LOADING,
|
||||||
|
APPROVED_AND_PERMITTED,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Permit {
|
export interface Permit {
|
||||||
state: PermitState
|
state: PermitState
|
||||||
isSyncing?: boolean
|
|
||||||
signature?: PermitSignature
|
signature?: PermitSignature
|
||||||
callback?: () => Promise<{
|
callback?: () => Promise<{
|
||||||
response: ContractTransaction
|
response: ContractTransaction
|
||||||
@@ -81,9 +81,8 @@ export default function usePermit(amount?: CurrencyAmount<Token>, spender?: stri
|
|||||||
|
|
||||||
// Permit2 should be marked syncing from the time approval is submitted (pending) until it is
|
// Permit2 should be marked syncing from the time approval is submitted (pending) until it is
|
||||||
// synced in tokenAllowance, to avoid re-prompting the user for an already-submitted approval.
|
// synced in tokenAllowance, to avoid re-prompting the user for an already-submitted approval.
|
||||||
// It should *not* be marked syncing if not permitted, because the user must still take action.
|
|
||||||
const [syncState, setSyncState] = useState(SyncState.SYNCED)
|
const [syncState, setSyncState] = useState(SyncState.SYNCED)
|
||||||
const isSyncing = isPermitted || isSigned ? false : syncState !== SyncState.SYNCED
|
const isApprovalLoading = syncState !== SyncState.SYNCED
|
||||||
const hasPendingApproval = useHasPendingApproval(amount?.currency, PERMIT2_ADDRESS)
|
const hasPendingApproval = useHasPendingApproval(amount?.currency, PERMIT2_ADDRESS)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasPendingApproval) {
|
if (hasPendingApproval) {
|
||||||
@@ -117,13 +116,25 @@ export default function usePermit(amount?: CurrencyAmount<Token>, spender?: stri
|
|||||||
return { state: PermitState.INVALID }
|
return { state: PermitState.INVALID }
|
||||||
} else if (!tokenAllowance || !permitAllowance) {
|
} else if (!tokenAllowance || !permitAllowance) {
|
||||||
return { state: PermitState.LOADING }
|
return { state: PermitState.LOADING }
|
||||||
} else if (isAllowed) {
|
} else if (!(isPermitted || isSigned)) {
|
||||||
if (isPermitted) {
|
return { state: PermitState.APPROVAL_OR_PERMIT_NEEDED, callback }
|
||||||
return { state: PermitState.PERMITTED }
|
} else if (!isAllowed) {
|
||||||
} else if (isSigned) {
|
return {
|
||||||
return { state: PermitState.PERMITTED, signature }
|
state: isApprovalLoading ? PermitState.APPROVAL_LOADING : PermitState.APPROVAL_OR_PERMIT_NEEDED,
|
||||||
|
callback,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return { state: PermitState.APPROVED_AND_PERMITTED, signature: isPermitted ? undefined : signature }
|
||||||
}
|
}
|
||||||
return { state: PermitState.PERMIT_NEEDED, isSyncing, callback }
|
}, [
|
||||||
}, [amount, callback, isAllowed, isPermitted, isSigned, isSyncing, permitAllowance, signature, tokenAllowance])
|
amount,
|
||||||
|
callback,
|
||||||
|
isAllowed,
|
||||||
|
isApprovalLoading,
|
||||||
|
isPermitted,
|
||||||
|
isSigned,
|
||||||
|
permitAllowance,
|
||||||
|
signature,
|
||||||
|
tokenAllowance,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import Loader from 'components/Loader'
|
|||||||
import { MenuDropdown } from 'components/NavBar/MenuDropdown'
|
import { MenuDropdown } from 'components/NavBar/MenuDropdown'
|
||||||
import TopLevelModals from 'components/TopLevelModals'
|
import TopLevelModals from 'components/TopLevelModals'
|
||||||
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||||
import { LandingPageVariant, useLandingPageFlag } from 'featureFlags/flags/landingPage'
|
|
||||||
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
|
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
|
||||||
import { Box } from 'nft/components/Box'
|
import { Box } from 'nft/components/Box'
|
||||||
import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton'
|
import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton'
|
||||||
@@ -37,6 +36,7 @@ import Manage from './Earn/Manage'
|
|||||||
import Landing from './Landing'
|
import Landing from './Landing'
|
||||||
import MigrateV2 from './MigrateV2'
|
import MigrateV2 from './MigrateV2'
|
||||||
import MigrateV2Pair from './MigrateV2/MigrateV2Pair'
|
import MigrateV2Pair from './MigrateV2/MigrateV2Pair'
|
||||||
|
import NotFound from './NotFound'
|
||||||
import Pool from './Pool'
|
import Pool from './Pool'
|
||||||
import { PositionPage } from './Pool/PositionPage'
|
import { PositionPage } from './Pool/PositionPage'
|
||||||
import PoolV2 from './Pool/v2'
|
import PoolV2 from './Pool/v2'
|
||||||
@@ -65,29 +65,19 @@ initializeAnalytics(ANALYTICS_DUMMY_KEY, OriginApplication.INTERFACE, {
|
|||||||
isProductionEnv: isProductionEnv(),
|
isProductionEnv: isProductionEnv(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const AppWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
height: 100%;
|
|
||||||
`
|
|
||||||
|
|
||||||
const BodyWrapper = styled.div`
|
const BodyWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
min-height: 100vh;
|
||||||
padding: 72px 0px 0px 0px;
|
padding: ${({ theme }) => theme.navHeight}px 0px 5rem 0px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
|
||||||
padding: 52px 0px 16px 0px;
|
|
||||||
`};
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const MobileBottomBar = styled.div`
|
const MobileBottomBar = styled.div`
|
||||||
z-index: ${Z_INDEX.sticky};
|
z-index: ${Z_INDEX.sticky};
|
||||||
position: sticky;
|
position: fixed;
|
||||||
display: flex;
|
display: flex;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -115,10 +105,6 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>`
|
|||||||
z-index: ${Z_INDEX.sticky};
|
z-index: ${Z_INDEX.sticky};
|
||||||
`
|
`
|
||||||
|
|
||||||
const Marginer = styled.div`
|
|
||||||
margin-top: 5rem;
|
|
||||||
`
|
|
||||||
|
|
||||||
function getCurrentPageFromLocation(locationPathname: string): PageName | undefined {
|
function getCurrentPageFromLocation(locationPathname: string): PageName | undefined {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case locationPathname.startsWith('/swap'):
|
case locationPathname.startsWith('/swap'):
|
||||||
@@ -202,134 +188,131 @@ export default function App() {
|
|||||||
|
|
||||||
const isHeaderTransparent = !scrolledState
|
const isHeaderTransparent = !scrolledState
|
||||||
|
|
||||||
const landingPageFlag = useLandingPageFlag()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<DarkModeQueryParamReader />
|
<DarkModeQueryParamReader />
|
||||||
<ApeModeQueryParamReader />
|
<ApeModeQueryParamReader />
|
||||||
<AppWrapper>
|
<Trace page={currentPage}>
|
||||||
<Trace page={currentPage}>
|
<HeaderWrapper transparent={isHeaderTransparent}>
|
||||||
<HeaderWrapper transparent={isHeaderTransparent}>
|
<NavBar />
|
||||||
<NavBar />
|
</HeaderWrapper>
|
||||||
</HeaderWrapper>
|
<BodyWrapper>
|
||||||
<BodyWrapper>
|
<Popups />
|
||||||
<Popups />
|
<Polling />
|
||||||
<Polling />
|
<TopLevelModals />
|
||||||
<TopLevelModals />
|
<Suspense fallback={<Loader />}>
|
||||||
<Suspense fallback={<Loader />}>
|
{isLoaded ? (
|
||||||
{isLoaded ? (
|
<Routes>
|
||||||
<Routes>
|
<Route path="/" element={<Landing />} />
|
||||||
{landingPageFlag === LandingPageVariant.Enabled && <Route path="/" element={<Landing />} />}
|
|
||||||
<Route path="tokens" element={<Tokens />}>
|
|
||||||
<Route path=":chainName" />
|
|
||||||
</Route>
|
|
||||||
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
|
|
||||||
<Route
|
|
||||||
path="vote/*"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={<LazyLoadSpinner />}>
|
|
||||||
<Vote />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<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="tokens" element={<Tokens />}>
|
||||||
<Route path="swap" element={<Swap />} />
|
<Route path=":chainName" />
|
||||||
|
</Route>
|
||||||
|
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
|
||||||
|
<Route
|
||||||
|
path="vote/*"
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<LazyLoadSpinner />}>
|
||||||
|
<Vote />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<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="pool/v2/find" element={<PoolFinder />} />
|
<Route path="send" element={<RedirectPathToSwapOnly />} />
|
||||||
<Route path="pool/v2" element={<PoolV2 />} />
|
<Route path="swap" element={<Swap />} />
|
||||||
<Route path="pool" element={<Pool />} />
|
|
||||||
<Route path="pool/:tokenId" element={<PositionPage />} />
|
|
||||||
|
|
||||||
<Route path="add/v2" element={<RedirectDuplicateTokenIdsV2 />}>
|
<Route path="pool/v2/find" element={<PoolFinder />} />
|
||||||
<Route path=":currencyIdA" />
|
<Route path="pool/v2" element={<PoolV2 />} />
|
||||||
<Route path=":currencyIdA/:currencyIdB" />
|
<Route path="pool" element={<Pool />} />
|
||||||
</Route>
|
<Route path="pool/:tokenId" element={<PositionPage />} />
|
||||||
<Route path="add" element={<RedirectDuplicateTokenIds />}>
|
|
||||||
{/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
|
|
||||||
<Route path=":currencyIdA" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="increase" element={<AddLiquidity />}>
|
<Route path="add/v2" element={<RedirectDuplicateTokenIdsV2 />}>
|
||||||
<Route path=":currencyIdA" />
|
<Route path=":currencyIdA" />
|
||||||
<Route path=":currencyIdA/:currencyIdB" />
|
<Route path=":currencyIdA/:currencyIdB" />
|
||||||
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
|
</Route>
|
||||||
<Route path=":currencyIdA/:currencyIdB/:feeAmount/:tokenId" />
|
<Route path="add" element={<RedirectDuplicateTokenIds />}>
|
||||||
</Route>
|
{/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
|
||||||
|
<Route path=":currencyIdA" />
|
||||||
|
<Route path=":currencyIdA/:currencyIdB" />
|
||||||
|
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="remove/v2/:currencyIdA/:currencyIdB" element={<RemoveLiquidity />} />
|
<Route path="increase" element={<AddLiquidity />}>
|
||||||
<Route path="remove/:tokenId" element={<RemoveLiquidityV3 />} />
|
<Route path=":currencyIdA" />
|
||||||
|
<Route path=":currencyIdA/:currencyIdB" />
|
||||||
|
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
|
||||||
|
<Route path=":currencyIdA/:currencyIdB/:feeAmount/:tokenId" />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="migrate/v2" element={<MigrateV2 />} />
|
<Route path="remove/v2/:currencyIdA/:currencyIdB" element={<RemoveLiquidity />} />
|
||||||
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
|
<Route path="remove/:tokenId" element={<RemoveLiquidityV3 />} />
|
||||||
|
|
||||||
<Route path="about" element={<About />} />
|
<Route path="migrate/v2" element={<MigrateV2 />} />
|
||||||
|
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
|
||||||
|
|
||||||
<Route path="*" element={<RedirectPathToSwapOnly />} />
|
<Route path="about" element={<About />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/nfts"
|
path="/nfts"
|
||||||
element={
|
element={
|
||||||
// TODO: replace loading state during Apollo migration
|
// TODO: replace loading state during Apollo migration
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<NftExplore />
|
<NftExplore />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/nfts/asset/:contractAddress/:tokenId"
|
path="/nfts/asset/:contractAddress/:tokenId"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<AssetDetailsLoading />}>
|
<Suspense fallback={<AssetDetailsLoading />}>
|
||||||
<Asset />
|
<Asset />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/nfts/profile"
|
path="/nfts/profile"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<ProfilePageLoadingSkeleton />}>
|
<Suspense fallback={<ProfilePageLoadingSkeleton />}>
|
||||||
<Profile />
|
<Profile />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/nfts/collection/:contractAddress"
|
path="/nfts/collection/:contractAddress"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<CollectionPageSkeleton />}>
|
<Suspense fallback={<CollectionPageSkeleton />}>
|
||||||
<Collection />
|
<Collection />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/nfts/collection/:contractAddress/activity"
|
path="/nfts/collection/:contractAddress/activity"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<CollectionPageSkeleton />}>
|
<Suspense fallback={<CollectionPageSkeleton />}>
|
||||||
<Collection />
|
<Collection />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
|
||||||
) : (
|
<Route path="*" element={<Navigate to="/not-found" replace />} />
|
||||||
<Loader />
|
<Route path="/not-found" element={<NotFound />} />
|
||||||
)}
|
</Routes>
|
||||||
</Suspense>
|
) : (
|
||||||
<Marginer />
|
<Loader />
|
||||||
</BodyWrapper>
|
)}
|
||||||
<MobileBottomBar>
|
</Suspense>
|
||||||
<PageTabs />
|
</BodyWrapper>
|
||||||
<Box marginY="4">
|
<MobileBottomBar>
|
||||||
<MenuDropdown />
|
<PageTabs />
|
||||||
</Box>
|
<Box marginY="4">
|
||||||
</MobileBottomBar>
|
<MenuDropdown />
|
||||||
</Trace>
|
</Box>
|
||||||
</AppWrapper>
|
</MobileBottomBar>
|
||||||
|
</Trace>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Trace, TraceEvent } from '@uniswap/analytics'
|
import { Trace, TraceEvent } from '@uniswap/analytics'
|
||||||
import { BrowserEvent, ElementName, EventName, PageName } from '@uniswap/analytics-events'
|
import { BrowserEvent, ElementName, EventName, PageName } from '@uniswap/analytics-events'
|
||||||
import { BaseButton } from 'components/Button'
|
import { BaseButton } from 'components/Button'
|
||||||
import { LandingPageVariant, useLandingPageFlag } from 'featureFlags/flags/landingPage'
|
|
||||||
import Swap from 'pages/Swap'
|
import Swap from 'pages/Swap'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
@@ -170,21 +169,14 @@ export default function Landing() {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const isOpen = location.pathname === '/'
|
const isOpen = location.pathname === '/'
|
||||||
|
|
||||||
const landingPageFlag = useLandingPageFlag()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (landingPageFlag) {
|
document.body.style.overflow = 'hidden'
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
return () => {
|
|
||||||
document.body.style.overflow = 'auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return () => {
|
return () => {
|
||||||
// need to have a return so the hook doesn't throw.
|
document.body.style.overflow = 'auto'
|
||||||
}
|
}
|
||||||
}, [landingPageFlag])
|
}, [])
|
||||||
|
|
||||||
if (landingPageFlag === LandingPageVariant.Control || !isOpen) return null
|
if (!isOpen) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Trace page={PageName.LANDING_PAGE} shouldLogImpression>
|
<Trace page={PageName.LANDING_PAGE} shouldLogImpression>
|
||||||
|
|||||||
65
src/pages/NotFound/index.tsx
Normal file
65
src/pages/NotFound/index.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Trans } from '@lingui/macro'
|
||||||
|
import { Trace } from '@uniswap/analytics'
|
||||||
|
import { SmallButtonPrimary } from 'components/Button'
|
||||||
|
import { useIsMobile } from 'nft/hooks'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { useIsDarkMode } from 'state/user/hooks'
|
||||||
|
import styled from 'styled-components/macro'
|
||||||
|
import { ThemedText } from 'theme'
|
||||||
|
|
||||||
|
import darkImage from '../../assets/images/404-page-dark.png'
|
||||||
|
import lightImage from '../../assets/images/404-page-light.png'
|
||||||
|
|
||||||
|
const Image = styled.img`
|
||||||
|
max-width: 510px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 75px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Header = styled(Container)`
|
||||||
|
gap: 30px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PageWrapper = styled(Container)`
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 50px;
|
||||||
|
|
||||||
|
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 64px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
const isDarkMode = useIsDarkMode()
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
|
const Title = isMobile ? ThemedText.LargeHeader : ThemedText.Hero
|
||||||
|
const Paragraph = isMobile ? ThemedText.HeadlineMedium : ThemedText.HeadlineLarge
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper>
|
||||||
|
<Trace page="404-page" shouldLogImpression>
|
||||||
|
<Header>
|
||||||
|
<Container>
|
||||||
|
<Title>404</Title>
|
||||||
|
<Paragraph color="textSecondary">
|
||||||
|
<Trans>Page not found!</Trans>
|
||||||
|
</Paragraph>
|
||||||
|
</Container>
|
||||||
|
<Image src={isDarkMode ? darkImage : lightImage} alt="Liluni" />
|
||||||
|
</Header>
|
||||||
|
<SmallButtonPrimary as={Link} to="/">
|
||||||
|
<Trans>Oops, take me back to Swap</Trans>
|
||||||
|
</SmallButtonPrimary>
|
||||||
|
</Trace>
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -165,7 +165,7 @@ function WrongNetworkCard() {
|
|||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100vh' }}>
|
<>
|
||||||
<PageWrapper>
|
<PageWrapper>
|
||||||
<AutoColumn gap="lg" justify="center">
|
<AutoColumn gap="lg" justify="center">
|
||||||
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
||||||
@@ -189,7 +189,7 @@ function WrongNetworkCard() {
|
|||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
<SwitchLocaleLink />
|
<SwitchLocaleLink />
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,86 +263,84 @@ export default function Pool() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Trace page={PageName.POOL_PAGE} shouldLogImpression>
|
<Trace page={PageName.POOL_PAGE} shouldLogImpression>
|
||||||
<div style={{ height: '100vh' }}>
|
<PageWrapper>
|
||||||
<PageWrapper>
|
<AutoColumn gap="lg" justify="center">
|
||||||
<AutoColumn gap="lg" justify="center">
|
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
||||||
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
<TitleRow padding="0">
|
||||||
<TitleRow padding="0">
|
<ThemedText.LargeHeader>
|
||||||
<ThemedText.LargeHeader>
|
<Trans>Pools</Trans>
|
||||||
<Trans>Pools</Trans>
|
</ThemedText.LargeHeader>
|
||||||
</ThemedText.LargeHeader>
|
<ButtonRow>
|
||||||
<ButtonRow>
|
{showV2Features && (
|
||||||
{showV2Features && (
|
<PoolMenu
|
||||||
<PoolMenu
|
menuItems={menuItems}
|
||||||
menuItems={menuItems}
|
flyoutAlignment={FlyoutAlignment.LEFT}
|
||||||
flyoutAlignment={FlyoutAlignment.LEFT}
|
ToggleUI={(props: any) => (
|
||||||
ToggleUI={(props: any) => (
|
<MoreOptionsButton {...props}>
|
||||||
<MoreOptionsButton {...props}>
|
<MoreOptionsText>
|
||||||
<MoreOptionsText>
|
<Trans>More</Trans>
|
||||||
<Trans>More</Trans>
|
<ChevronDown size={15} />
|
||||||
<ChevronDown size={15} />
|
</MoreOptionsText>
|
||||||
</MoreOptionsText>
|
</MoreOptionsButton>
|
||||||
</MoreOptionsButton>
|
)}
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ResponsiveButtonPrimary data-cy="join-pool-button" id="join-pool-button" as={Link} to="/add/ETH">
|
|
||||||
+ <Trans>New Position</Trans>
|
|
||||||
</ResponsiveButtonPrimary>
|
|
||||||
</ButtonRow>
|
|
||||||
</TitleRow>
|
|
||||||
|
|
||||||
<MainContentWrapper>
|
|
||||||
{positionsLoading ? (
|
|
||||||
<PositionsLoadingPlaceholder />
|
|
||||||
) : filteredPositions && closedPositions && filteredPositions.length > 0 ? (
|
|
||||||
<PositionList
|
|
||||||
positions={filteredPositions}
|
|
||||||
setUserHideClosedPositions={setUserHideClosedPositions}
|
|
||||||
userHideClosedPositions={userHideClosedPositions}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<ErrorContainer>
|
|
||||||
<ThemedText.DeprecatedBody color={theme.textTertiary} textAlign="center">
|
|
||||||
<InboxIcon strokeWidth={1} style={{ marginTop: '2em' }} />
|
|
||||||
<div>
|
|
||||||
<Trans>Your active V3 liquidity positions will appear here.</Trans>
|
|
||||||
</div>
|
|
||||||
</ThemedText.DeprecatedBody>
|
|
||||||
{!showConnectAWallet && closedPositions.length > 0 && (
|
|
||||||
<ButtonText
|
|
||||||
style={{ marginTop: '.5rem' }}
|
|
||||||
onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}
|
|
||||||
>
|
|
||||||
<Trans>Show closed positions</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
)}
|
|
||||||
{showConnectAWallet && (
|
|
||||||
<TraceEvent
|
|
||||||
events={[BrowserEvent.onClick]}
|
|
||||||
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
|
|
||||||
properties={{ received_swap_quote: false }}
|
|
||||||
element={ElementName.CONNECT_WALLET_BUTTON}
|
|
||||||
>
|
|
||||||
<ButtonPrimary
|
|
||||||
style={{ marginTop: '2em', marginBottom: '2em', padding: '8px 16px' }}
|
|
||||||
onClick={toggleWalletModal}
|
|
||||||
>
|
|
||||||
<Trans>Connect a wallet</Trans>
|
|
||||||
</ButtonPrimary>
|
|
||||||
</TraceEvent>
|
|
||||||
)}
|
|
||||||
</ErrorContainer>
|
|
||||||
)}
|
)}
|
||||||
</MainContentWrapper>
|
<ResponsiveButtonPrimary data-cy="join-pool-button" id="join-pool-button" as={Link} to="/add/ETH">
|
||||||
<HideSmall>
|
+ <Trans>New Position</Trans>
|
||||||
<CTACards />
|
</ResponsiveButtonPrimary>
|
||||||
</HideSmall>
|
</ButtonRow>
|
||||||
</AutoColumn>
|
</TitleRow>
|
||||||
|
|
||||||
|
<MainContentWrapper>
|
||||||
|
{positionsLoading ? (
|
||||||
|
<PositionsLoadingPlaceholder />
|
||||||
|
) : filteredPositions && closedPositions && filteredPositions.length > 0 ? (
|
||||||
|
<PositionList
|
||||||
|
positions={filteredPositions}
|
||||||
|
setUserHideClosedPositions={setUserHideClosedPositions}
|
||||||
|
userHideClosedPositions={userHideClosedPositions}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ErrorContainer>
|
||||||
|
<ThemedText.DeprecatedBody color={theme.textTertiary} textAlign="center">
|
||||||
|
<InboxIcon strokeWidth={1} style={{ marginTop: '2em' }} />
|
||||||
|
<div>
|
||||||
|
<Trans>Your active V3 liquidity positions will appear here.</Trans>
|
||||||
|
</div>
|
||||||
|
</ThemedText.DeprecatedBody>
|
||||||
|
{!showConnectAWallet && closedPositions.length > 0 && (
|
||||||
|
<ButtonText
|
||||||
|
style={{ marginTop: '.5rem' }}
|
||||||
|
onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}
|
||||||
|
>
|
||||||
|
<Trans>Show closed positions</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
)}
|
||||||
|
{showConnectAWallet && (
|
||||||
|
<TraceEvent
|
||||||
|
events={[BrowserEvent.onClick]}
|
||||||
|
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
|
||||||
|
properties={{ received_swap_quote: false }}
|
||||||
|
element={ElementName.CONNECT_WALLET_BUTTON}
|
||||||
|
>
|
||||||
|
<ButtonPrimary
|
||||||
|
style={{ marginTop: '2em', marginBottom: '2em', padding: '8px 16px' }}
|
||||||
|
onClick={toggleWalletModal}
|
||||||
|
>
|
||||||
|
<Trans>Connect a wallet</Trans>
|
||||||
|
</ButtonPrimary>
|
||||||
|
</TraceEvent>
|
||||||
|
)}
|
||||||
|
</ErrorContainer>
|
||||||
|
)}
|
||||||
|
</MainContentWrapper>
|
||||||
|
<HideSmall>
|
||||||
|
<CTACards />
|
||||||
|
</HideSmall>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</PageWrapper>
|
</AutoColumn>
|
||||||
<SwitchLocaleLink />
|
</PageWrapper>
|
||||||
</div>
|
<SwitchLocaleLink />
|
||||||
</Trace>
|
</Trace>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export default function Swap({ className }: { className?: string }) {
|
|||||||
permit2Enabled ? maximumAmountIn : undefined,
|
permit2Enabled ? maximumAmountIn : undefined,
|
||||||
permit2Enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
|
permit2Enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
|
||||||
)
|
)
|
||||||
const isApprovalPending = permit.isSyncing
|
const isApprovalLoading = permit.state === PermitState.APPROVAL_LOADING
|
||||||
const [isPermitPending, setIsPermitPending] = useState(false)
|
const [isPermitPending, setIsPermitPending] = useState(false)
|
||||||
const [isPermitFailed, setIsPermitFailed] = useState(false)
|
const [isPermitFailed, setIsPermitFailed] = useState(false)
|
||||||
const addTransaction = useTransactionAdder()
|
const addTransaction = useTransactionAdder()
|
||||||
@@ -786,10 +786,12 @@ export default function Swap({ className }: { className?: string }) {
|
|||||||
</ButtonError>
|
</ButtonError>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</AutoRow>
|
</AutoRow>
|
||||||
) : isValid && permit.state === PermitState.PERMIT_NEEDED ? (
|
) : isValid &&
|
||||||
|
(permit.state === PermitState.APPROVAL_OR_PERMIT_NEEDED ||
|
||||||
|
permit.state === PermitState.APPROVAL_LOADING) ? (
|
||||||
<ButtonYellow
|
<ButtonYellow
|
||||||
onClick={updatePermit}
|
onClick={updatePermit}
|
||||||
disabled={isPermitPending || isApprovalPending}
|
disabled={isPermitPending || isApprovalLoading}
|
||||||
style={{ gap: 14 }}
|
style={{ gap: 14 }}
|
||||||
>
|
>
|
||||||
{isPermitPending ? (
|
{isPermitPending ? (
|
||||||
@@ -806,7 +808,7 @@ export default function Swap({ className }: { className?: string }) {
|
|||||||
<Trans>Approval failed. Try again.</Trans>
|
<Trans>Approval failed. Try again.</Trans>
|
||||||
</ThemedText.SubHeader>
|
</ThemedText.SubHeader>
|
||||||
</>
|
</>
|
||||||
) : isApprovalPending ? (
|
) : isApprovalLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader size="20px" stroke={theme.accentWarning} />
|
<Loader size="20px" stroke={theme.accentWarning} />
|
||||||
<ThemedText.SubHeader color="accentWarning">
|
<ThemedText.SubHeader color="accentWarning">
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { filterTimeAtom } from 'components/Tokens/state'
|
|
||||||
import TokenDetails from 'components/Tokens/TokenDetails'
|
import TokenDetails from 'components/Tokens/TokenDetails'
|
||||||
import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleton'
|
import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleton'
|
||||||
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||||
import { TokenQuery, tokenQuery } from 'graphql/data/Token'
|
import { TokenQuery, tokenQuery } from 'graphql/data/Token'
|
||||||
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
|
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
|
||||||
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod, toHistoryDuration, validateUrlChainParam } from 'graphql/data/util'
|
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod, toHistoryDuration, validateUrlChainParam } from 'graphql/data/util'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtom } from 'jotai'
|
||||||
|
import { atomWithStorage } from 'jotai/utils'
|
||||||
import { Suspense, useCallback, useEffect, useMemo } from 'react'
|
import { Suspense, useCallback, useEffect, useMemo } from 'react'
|
||||||
import { useQueryLoader } from 'react-relay'
|
import { useQueryLoader } from 'react-relay'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
export const pageTimePeriodAtom = atomWithStorage<TimePeriod>('tokenDetailsTimePeriod', TimePeriod.DAY)
|
||||||
|
|
||||||
export default function TokenDetailsPage() {
|
export default function TokenDetailsPage() {
|
||||||
const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>()
|
const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>()
|
||||||
const chain = validateUrlChainParam(chainName)
|
const chain = validateUrlChainParam(chainName)
|
||||||
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||||
const isNative = tokenAddress === NATIVE_CHAIN_ID
|
const isNative = tokenAddress === NATIVE_CHAIN_ID
|
||||||
const timePeriod = useAtomValue(filterTimeAtom)
|
const [timePeriod, setTimePeriod] = useAtom(pageTimePeriodAtom)
|
||||||
const [contract, duration] = useMemo(
|
const [contract, duration] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ address: isNative ? nativeOnChain(pageChainId).wrapped.address : tokenAddress ?? '', chain },
|
{ address: isNative ? nativeOnChain(pageChainId).wrapped.address : tokenAddress ?? '', chain },
|
||||||
@@ -35,8 +37,9 @@ export default function TokenDetailsPage() {
|
|||||||
const refetchTokenPrices = useCallback(
|
const refetchTokenPrices = useCallback(
|
||||||
(t: TimePeriod) => {
|
(t: TimePeriod) => {
|
||||||
loadPriceQuery({ contract, duration: toHistoryDuration(t) })
|
loadPriceQuery({ contract, duration: toHistoryDuration(t) })
|
||||||
|
setTimePeriod(t)
|
||||||
},
|
},
|
||||||
[contract, loadPriceQuery]
|
[contract, loadPriceQuery, setTimePeriod]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!tokenQueryReference) {
|
if (!tokenQueryReference) {
|
||||||
|
|||||||
@@ -31,12 +31,18 @@ export const ThemedText = {
|
|||||||
HeadlineSmall(props: TextProps) {
|
HeadlineSmall(props: TextProps) {
|
||||||
return <TextWrapper fontWeight={600} fontSize={20} lineHeight="28px" color="textPrimary" {...props} />
|
return <TextWrapper fontWeight={600} fontSize={20} lineHeight="28px" color="textPrimary" {...props} />
|
||||||
},
|
},
|
||||||
|
HeadlineMedium(props: TextProps) {
|
||||||
|
return <TextWrapper fontWeight={500} fontSize={28} color="textPrimary" {...props} />
|
||||||
|
},
|
||||||
HeadlineLarge(props: TextProps) {
|
HeadlineLarge(props: TextProps) {
|
||||||
return <TextWrapper fontWeight={600} fontSize={36} lineHeight="44px" color="textPrimary" {...props} />
|
return <TextWrapper fontWeight={600} fontSize={36} lineHeight="36px" color="textPrimary" {...props} />
|
||||||
},
|
},
|
||||||
LargeHeader(props: TextProps) {
|
LargeHeader(props: TextProps) {
|
||||||
return <TextWrapper fontWeight={400} fontSize={36} color="textPrimary" {...props} />
|
return <TextWrapper fontWeight={400} fontSize={36} color="textPrimary" {...props} />
|
||||||
},
|
},
|
||||||
|
Hero(props: TextProps) {
|
||||||
|
return <TextWrapper fontWeight={500} fontSize={48} color="textPrimary" {...props} />
|
||||||
|
},
|
||||||
Link(props: TextProps) {
|
Link(props: TextProps) {
|
||||||
return <TextWrapper fontWeight={600} fontSize={14} color="accentAction" {...props} />
|
return <TextWrapper fontWeight={600} fontSize={14} color="accentAction" {...props} />
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ function getSettings(darkMode: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTheme(darkMode: boolean) {
|
// eslint-disable-next-line import/no-unused-modules -- used in styled.d.ts
|
||||||
|
export function getTheme(darkMode: boolean) {
|
||||||
return {
|
return {
|
||||||
darkMode,
|
darkMode,
|
||||||
...(darkMode ? darkTheme : lightTheme),
|
...(darkMode ? darkTheme : lightTheme),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export function isTestEnv(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isStagingEnv(): boolean {
|
export function isStagingEnv(): boolean {
|
||||||
|
// NB: This is set in vercel builds.
|
||||||
return Boolean(process.env.REACT_APP_STAGING)
|
return Boolean(process.env.REACT_APP_STAGING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4438,10 +4438,10 @@
|
|||||||
"@uniswap/v3-core" "1.0.0"
|
"@uniswap/v3-core" "1.0.0"
|
||||||
"@uniswap/v3-periphery" "^1.0.1"
|
"@uniswap/v3-periphery" "^1.0.1"
|
||||||
|
|
||||||
"@uniswap/widgets@2.22.10":
|
"@uniswap/widgets@2.22.11":
|
||||||
version "2.22.10"
|
version "2.22.11"
|
||||||
resolved "https://registry.yarnpkg.com/@uniswap/widgets/-/widgets-2.22.10.tgz#3b4fbe3ca607c8b096aae58bd6e6d4188af7ddd5"
|
resolved "https://registry.yarnpkg.com/@uniswap/widgets/-/widgets-2.22.11.tgz#d64179e58d3923af1b80f63bd8fb44804fc047bd"
|
||||||
integrity sha512-wFw68p9fiVt06rernsWWqlB3lccgfyCuHTGNFT2NPBQ1lnpxudpa6MKlsMih5gaWiZM1pT8jd3LYbgCX9OeOTw==
|
integrity sha512-eBG7L/inLLHY2+cLKFsqqCK8rhoIzHWsUDm0glpLUyqs6NiZssoKHgIPWzjT2cNwupCYol08ruKM+JR1wBVdTQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" ">=7.17.0"
|
"@babel/runtime" ">=7.17.0"
|
||||||
"@fontsource/ibm-plex-mono" "^4.5.1"
|
"@fontsource/ibm-plex-mono" "^4.5.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user