Commit ab1538b1 authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

feature(position-list): add summary page for positions with mock data (#28)

* feature(position-list): add summary page for positions with mock data

* add loading states

* mobile layout
parent 7e5a230a
import { ThemeProvider as SCThemeProvider } from 'styled-components'
import 'inter-ui'
import { Story } from '@storybook/react/types-6-0'
import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core'
import React from 'react'
import { Provider as StoreProvider } from 'react-redux'
import { ThemeProvider as SCThemeProvider } from 'styled-components'
import { NetworkContextName } from '../src/constants'
import store from '../src/state'
import { FixedGlobalStyle, theme, ThemedGlobalStyle } from '../src/theme'
import getLibrary from '../src/utils/getLibrary'
import * as storybookThemes from './theme'
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
dependencies: {
withStoriesOnly: true,
hideEmpty: true
hideEmpty: true,
},
docs: {
theme: storybookThemes.light
theme: storybookThemes.light,
},
viewport: {
viewports: {
......@@ -19,32 +25,32 @@ export const parameters = {
name: 'iPhone X',
styles: {
width: '375px',
height: '812px'
}
height: '812px',
},
},
tablet: {
name: 'iPad',
styles: {
width: '768px',
height: '1024px'
}
height: '1024px',
},
},
laptop: {
name: 'Laptop',
styles: {
width: '1024px',
height: '768px'
}
height: '768px',
},
},
desktop: {
name: 'Desktop',
styles: {
width: '1440px',
height: '1024px'
}
}
}
}
height: '1024px',
},
},
},
},
}
export const globalTypes = {
......@@ -54,22 +60,30 @@ export const globalTypes = {
defaultValue: 'light',
toolbar: {
icon: 'circlehollow',
items: ['light', 'dark']
}
}
items: ['light', 'dark'],
},
},
}
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
const withProviders = (Component: Story, context: Record<string, any>) => {
const THEME = theme(context.globals.theme === 'dark')
return (
<>
<FixedGlobalStyle />
<SCThemeProvider theme={THEME}>
<ThemedGlobalStyle />
<main>
<Component />
</main>
</SCThemeProvider>
<Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}>
<StoreProvider store={store}>
<SCThemeProvider theme={THEME}>
<FixedGlobalStyle />
<ThemedGlobalStyle />
<main>
<Component />
</main>
</SCThemeProvider>
</StoreProvider>
</Web3ProviderNetwork>
</Web3ReactProvider>
</>
)
}
......
......@@ -4,9 +4,4 @@ describe('Pool', () => {
cy.get('#join-pool-button').click()
cy.url().should('contain', '/add/ETH')
})
it('import pool links to /import', () => {
cy.get('#import-pool-link').click()
cy.url().should('contain', '/find')
})
})
import { Story } from '@storybook/react/types-6-0'
import React, { PropsWithChildren } from 'react'
import Component, { BadgeProps, BadgeVariant } from './index'
export default {
title: 'Badge',
argTypes: {
variant: {
name: 'variant',
type: { name: 'string', require: false },
defaultValue: BadgeVariant.DEFAULT,
description: 'badge variant',
control: {
type: 'select',
options: Object.values(BadgeVariant),
},
},
},
args: {
children: '🦄 UNISWAP 🦄',
},
}
const Template: Story<PropsWithChildren<BadgeProps>> = (args) => <Component {...args}>{args.children}</Component>
export const DefaultBadge = Template.bind({})
DefaultBadge.args = {
variant: BadgeVariant.DEFAULT,
}
export const WarningBadge = Template.bind({})
WarningBadge.args = {
variant: BadgeVariant.WARNING,
}
export const NegativeBadge = Template.bind({})
NegativeBadge.args = {
variant: BadgeVariant.NEGATIVE,
}
export const PositiveBadge = Template.bind({})
PositiveBadge.args = {
variant: BadgeVariant.POSITIVE,
}
import { readableColor } from 'polished'
import { PropsWithChildren } from 'react'
import styled, { DefaultTheme } from 'styled-components'
import { Color } from 'theme/styled'
export enum BadgeVariant {
DEFAULT = 'DEFAULT',
NEGATIVE = 'NEGATIVE',
POSITIVE = 'POSITIVE',
WARNING = 'WARNING',
WARNING_OUTLINE = 'WARNING_OUTLINE',
}
export interface BadgeProps {
variant?: BadgeVariant
}
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): Color {
switch (variant) {
case BadgeVariant.NEGATIVE:
return theme.error
case BadgeVariant.POSITIVE:
return theme.success
case BadgeVariant.WARNING:
return theme.warning
case BadgeVariant.WARNING_OUTLINE:
return 'transparent'
default:
return theme.bg3
}
}
function pickBorder(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
switch (variant) {
case BadgeVariant.WARNING_OUTLINE:
return `2px solid ${theme.warning}`
default:
return 'unset'
}
}
function pickFontColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
switch (variant) {
case BadgeVariant.NEGATIVE:
return readableColor(theme.error)
case BadgeVariant.POSITIVE:
return readableColor(theme.success)
case BadgeVariant.WARNING:
return readableColor(theme.warning)
case BadgeVariant.WARNING_OUTLINE:
return theme.warning
default:
return readableColor(theme.bg3)
}
}
const Badge = styled.div<PropsWithChildren<BadgeProps>>`
align-items: center;
background-color: ${({ theme, variant }) => pickBackgroundColor(variant, theme)};
border: ${({ theme, variant }) => pickBorder(variant, theme)};
border-radius: 0.5em;
color: ${({ theme, variant }) => pickFontColor(variant, theme)};
display: inline-flex;
padding: 2px 6px;
justify-content: center;
`
export default Badge
......@@ -29,6 +29,7 @@ export default function CurrencyLogo({
currency,
size = '24px',
style,
...rest
}: {
currency?: Currency
size?: string
......@@ -49,8 +50,8 @@ export default function CurrencyLogo({
}, [currency, uriLocations])
if (currency === ETHER) {
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} />
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} {...rest} />
}
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} />
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} {...rest} />
}
import { Story } from '@storybook/react/types-6-0'
import React from 'react'
import { DAI, WBTC } from '../../constants'
import Component, { DoubleCurrencyLogoProps } from './index'
export default {
title: 'DoubleCurrencyLogo',
decorators: [],
}
const Template: Story<DoubleCurrencyLogoProps> = (args) => <Component {...args} />
export const DoubleCurrencyLogo = Template.bind({})
DoubleCurrencyLogo.args = {
currency0: DAI,
currency1: WBTC,
size: 220,
}
......@@ -7,10 +7,10 @@ const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
position: relative;
display: flex;
flex-direction: row;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
margin-left: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
`
interface DoubleCurrencyLogoProps {
export interface DoubleCurrencyLogoProps {
margin?: boolean
size?: number
currency0?: Currency
......
import { Story } from '@storybook/react/types-6-0'
import React from 'react'
import styled from 'styled-components'
import Component from './index'
const Wrapper = styled.div`
max-width: 150px;
`
export default {
title: 'Menu',
decorators: [
() => (
<Wrapper>
<Component />
</Wrapper>
),
],
}
const Template: Story<any> = (args) => <Component {...args} />
export const Menu = Template.bind({})
Menu.args = {}
import React, { useRef } from 'react'
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
import styled from 'styled-components'
import styled, { css } from 'styled-components'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
......@@ -10,6 +10,11 @@ import { useModalOpen, useToggleModal } from '../../state/application/hooks'
import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button'
export enum FlyoutAlignment {
LEFT = 'LEFT',
RIGHT = 'RIGHT',
}
const StyledMenuIcon = styled(MenuIcon)`
path {
stroke: ${({ theme }) => theme.text1};
......@@ -51,7 +56,7 @@ const StyledMenu = styled.div`
text-align: left;
`
const MenuFlyout = styled.span`
const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
min-width: 8.125rem;
background-color: ${({ theme }) => theme.bg3};
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),
......@@ -63,9 +68,15 @@ const MenuFlyout = styled.span`
font-size: 1rem;
position: absolute;
top: 4rem;
right: 0rem;
z-index: 100;
${({ flyoutAlignment = FlyoutAlignment.RIGHT }) =>
flyoutAlignment === FlyoutAlignment.RIGHT
? css`
right: 0rem;
`
: css`
left: 0rem;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
top: -17.25rem;
`};
......@@ -135,3 +146,41 @@ export default function Menu() {
</StyledMenu>
)
}
interface NewMenuProps {
flyoutAlignment?: FlyoutAlignment
ToggleUI?: React.FunctionComponent
menuItems: {
content: any
link: string
}[]
}
const NewMenuFlyout = styled(MenuFlyout)`
top: 3rem !important;
`
const NewMenuItem = styled(MenuItem)`
width: max-content;
`
export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: NewMenuProps) => {
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
const toggle = useToggleModal(ApplicationModal.POOL_OVERVIEW_OPTIONS)
useOnClickOutside(node, open ? toggle : undefined)
const ToggleElement = ToggleUI || StyledMenuIcon
return (
<StyledMenu ref={node as any} {...rest}>
<ToggleElement onClick={toggle} />
{open && (
<NewMenuFlyout flyoutAlignment={flyoutAlignment}>
{menuItems.map(({ content, link }, i) => (
<NewMenuItem id="link" href={link} key={link + i}>
{content}
</NewMenuItem>
))}
</NewMenuFlyout>
)}
</StyledMenu>
)
}
import { Story } from '@storybook/react/types-6-0'
import { TokenAmount } from '@uniswap/sdk'
import React from 'react'
import { basisPointsToPercent } from 'utils'
import { DAI, WBTC } from '../../constants'
import Component, { PositionListProps } from './index'
const FEE_BIPS = {
FIVE: basisPointsToPercent(5),
THIRTY: basisPointsToPercent(30),
ONE_HUNDRED: basisPointsToPercent(100),
}
const daiAmount = new TokenAmount(DAI, BigInt(500) * BigInt(10e18))
const wbtcAmount = new TokenAmount(WBTC, BigInt(1) * BigInt(10e7))
const positions = [
{
feesEarned: {
DAI: 1000,
WBTC: 0.005,
},
feeLevel: FEE_BIPS.FIVE,
tokenAmount0: daiAmount,
tokenAmount1: wbtcAmount,
tickLower: 40000,
tickUpper: 60000,
},
{
feesEarned: {
DAI: 1000,
WBTC: 0.005,
},
feeLevel: FEE_BIPS.THIRTY,
tokenAmount0: daiAmount,
tokenAmount1: wbtcAmount,
tickLower: 45000,
tickUpper: 55000,
},
]
export default {
title: 'PositionList',
}
const Template: Story<PositionListProps> = (args) => <Component {...args} />
export const PositionList = Template.bind({})
PositionList.args = {
positions,
showUnwrapped: true,
}
import { Percent, TokenAmount } from '@uniswap/sdk'
import Badge, { BadgeVariant } from 'components/Badge'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import React from 'react'
import { AlertTriangle } from 'react-feather'
import { useTranslation } from 'react-i18next'
import { unwrappedToken } from 'utils/wrappedCurrency'
import styled, { keyframes } from 'styled-components'
import { Link } from 'react-router-dom'
import { MEDIA_WIDTHS } from 'theme'
const ActiveDot = styled.span`
background-color: ${({ theme }) => theme.success};
border-radius: 50%;
height: 8px;
width: 8px;
`
const Row = styled(Link)`
align-items: center;
background-color: ${({ theme }) => theme.bg2};
border-radius: 12px;
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.text1};
margin: 14px 0;
padding: 12px;
text-decoration: none;
&:first-of-type {
margin: 0 0 14px 0;
}
&:last-of-type {
margin: 14px 0 0 0;
}
& > div:not(:first-child) {
text-align: right;
min-width: 18%;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
flex-direction: row;
}
`
const BadgeText = styled.div`
font-weight: 600;
`
const BadgeWrapper = styled.div`
font-size: 12px;
`
const DataLineItem = styled.div`
text-align: right;
`
const DoubleArrow = styled.span`
color: ${({ theme }) => theme.text2};
`
const DesktopHeader = styled.div`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
align-items: center;
display: flex;
margin: 0 0 14px 0;
& > div:first-child {
flex: 1 1 auto;
}
& > div:not(:first-child) {
text-align: right;
min-width: 18%;
}
}
`
const loadingAnimation = keyframes`
0% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
`
const LoadingRows = styled.div`
display: grid;
grid-column-gap: 0.5em;
grid-row-gap: 0.8em;
grid-template-columns: repeat(3, 1fr);
& > div {
animation: ${loadingAnimation} 1.5s infinite;
animation-fill-mode: both;
background: linear-gradient(
to left,
${({ theme }) => theme.bg3} 25%,
${({ theme }) => theme.bg5} 50%,
${({ theme }) => theme.bg3} 75%
);
background-size: 400%;
border-radius: 0.2em;
height: 1.4em;
will-change: background-position;
}
& > div:nth-child(4n + 1) {
grid-column: 1 / 3;
}
& > div:nth-child(4n) {
grid-column: 3 / 4;
margin-bottom: 2em;
}
`
const RangeData = styled.div`
display: flex;
flex-direction: column;
width: 100%;
& > div {
align-items: center;
display: flex;
justify-content: space-between;
width: 100%;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: block;
& > div {
display: block;
}
}
`
const AmountData = styled.div`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: block;
}
`
const FeeData = styled.div`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: block;
}
`
const LabelData = styled.div`
align-items: center;
display: flex;
flex: 1 1 auto;
justify-content: space-between;
width: 100%;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: block;
}
`
const MobileHeader = styled.div`
font-weight: medium;
font-size: 16px;
margin-bottom: 16px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: none;
}
`
const PrimaryPositionIdData = styled.div`
display: flex;
flex-direction: row;
padding: 6px 0;
`
interface Position {
feeLevel: Percent
feesEarned: Record<string, number>
tokenAmount0: TokenAmount
tokenAmount1: TokenAmount
tickLower: number
tickUpper: number
}
export type PositionListProps = React.PropsWithChildren<{
loading: boolean
positions: Position[]
showUnwrapped?: boolean
}>
export default function PositionList({ loading, positions, showUnwrapped }: PositionListProps) {
const { t } = useTranslation()
return (
<>
<DesktopHeader>
<div>{t('Position')}</div>
<div>{t('Range')}</div>
<div>{t('Liquidity')}</div>
<div>{t('Fees Earned')}</div>
</DesktopHeader>
<MobileHeader>Your positions</MobileHeader>
{loading ? (
<LoadingRows>
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
</LoadingRows>
) : (
positions.map((position) => {
const { feeLevel, feesEarned, tokenAmount0, tokenAmount1 } = position
const symbol0 = tokenAmount0.token.symbol || ''
const symbol1 = tokenAmount1.token.symbol || ''
const currency0 = showUnwrapped ? tokenAmount0.token : unwrappedToken(tokenAmount0.token)
const currency1 = showUnwrapped ? tokenAmount1.token : unwrappedToken(tokenAmount1.token)
const limitCrossed = tokenAmount0.equalTo(BigInt(0)) || tokenAmount1.equalTo(BigInt(0))
const key = `${feeLevel.toFixed()}-${symbol0}-${tokenAmount0.toFixed(2)}-${symbol1}-${tokenAmount1.toFixed(
2
)}`
return (
<Row key={key} to="/asdf">
<LabelData>
<PrimaryPositionIdData>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={16} margin />
&nbsp;{symbol0}&nbsp;/&nbsp;{symbol1}
&nbsp;<Badge>{feeLevel.toSignificant(2)}%</Badge>
</PrimaryPositionIdData>
<BadgeWrapper>
{limitCrossed ? (
<Badge variant={BadgeVariant.WARNING}>
<AlertTriangle width={12} height={14} />
&nbsp;
<BadgeText>{t('Out of range')}</BadgeText>
</Badge>
) : (
<Badge variant={BadgeVariant.DEFAULT}>
<ActiveDot /> &nbsp;
<BadgeText>{t('Active')}</BadgeText>
</Badge>
)}
</BadgeWrapper>
</LabelData>
<RangeData>
<DataLineItem>
1,672 <DoubleArrow></DoubleArrow> 1,688 {symbol0}&nbsp;/&nbsp;{symbol1}
</DataLineItem>
<DataLineItem>
0.0002 <DoubleArrow></DoubleArrow> 0.0001 {symbol1}&nbsp;/&nbsp;{symbol0}
</DataLineItem>
</RangeData>
<AmountData>
<DataLineItem>
{tokenAmount0.toSignificant()}&nbsp;{symbol0}
</DataLineItem>
<DataLineItem>
{tokenAmount1.toSignificant()}&nbsp;{symbol1}
</DataLineItem>
</AmountData>
<FeeData>
<DataLineItem>
{feesEarned[symbol0]}&nbsp;{symbol0}
</DataLineItem>
<DataLineItem>
{feesEarned[symbol1]}&nbsp;{symbol1}
</DataLineItem>
</FeeData>
</Row>
)
})
)}
</>
)
}
import { Story } from '@storybook/react/types-6-0'
import React from 'react'
import Component from './index'
export default {
title: 'ThemeColorPalette',
}
const Template: Story<any> = (_args: any, context: Record<string, any>) => {
const isDarkMode = context.globals.theme === 'dark'
return <Component isDarkMode={isDarkMode} />
}
export const Palette = Template.bind({})
import { readableColor } from 'polished'
import React from 'react'
import styled from 'styled-components'
import { colors } from 'theme'
const Swatch = styled.div`
align-items: center;
display: flex;
flex-direction: column;
height: 100px;
justify-content: center;
min-width: 200px;
`
const Wrapper = styled.div`
display: flex;
flex-wrap: wrap;
flex-direction: row;
`
interface ThemePaletteProps {
isDarkMode: boolean
}
export default function ThemePalette({ isDarkMode }: ThemePaletteProps) {
const data = colors(isDarkMode)
return (
<Wrapper>
{Object.entries(data).map(([key, value]) => (
<Swatch key={key} style={{ color: readableColor(value), backgroundColor: value }}>
<div>{key}</div>
<div>{value}</div>
</Swatch>
))}
</Wrapper>
)
}
......@@ -22,7 +22,9 @@ import Manage from './Earn/Manage'
import MigrateV1 from './MigrateV1'
import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange'
import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange'
import MigrateV2 from './MigrateV2'
import Pool from './Pool'
import PoolV2 from './Pool/v2'
import PoolFinder from './PoolFinder'
import RemoveLiquidity from './RemoveLiquidity'
import { RedirectOldRemoveLiquidityPathStructure } from './RemoveLiquidity/redirects'
......@@ -95,6 +97,7 @@ export default function App() {
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/v2/pool" component={PoolV2} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/vote" component={Vote} />
<Route exact strict path="/create" component={RedirectToAddLiquidity} />
......@@ -109,6 +112,7 @@ export default function App() {
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/migrate/v1" component={MigrateV1} />
<Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} />
<Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/vote/:id" component={VotePage} />
<Route component={RedirectPathToSwapOnly} />
......
import React from 'react'
import { BodyWrapper } from '../AppBody'
export default function MigrateV1() {
return <BodyWrapper style={{ padding: 24 }}>migrate v2 liquidity to v3</BodyWrapper>
}
This diff is collapsed.
import React, { useContext, useMemo } from 'react'
import styled, { ThemeContext } from 'styled-components'
import { Pair, JSBI } from '@uniswap/sdk'
import { Link } from 'react-router-dom'
import { SwapPoolTabs } from '../../components/NavigationTabs'
import FullPositionCard from '../../components/PositionCard'
import { useUserHasLiquidityInAllTokens } from '../../data/V1'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { StyledInternalLink, ExternalLink, TYPE, HideSmall } from '../../theme'
import { Text } from 'rebass'
import Card from '../../components/Card'
import { RowBetween, RowFixed } from '../../components/Row'
import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { AutoColumn } from '../../components/Column'
import { useActiveWeb3React } from '../../hooks'
import { usePairs } from '../../data/Reserves'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { Dots } from '../../components/swap/styleds'
import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/earn/styled'
import { useStakingInfo } from '../../state/stake/hooks'
import { BIG_INT_ZERO } from '../../constants'
const PageWrapper = styled(AutoColumn)`
max-width: 640px;
width: 100%;
`
const VoteCard = styled(DataCard)`
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #27ae60 0%, #000000 100%);
overflow: hidden;
`
const TitleRow = styled(RowBetween)`
${({ theme }) => theme.mediaWidth.upToSmall`
flex-wrap: wrap;
gap: 12px;
width: 100%;
flex-direction: column-reverse;
`};
`
const ButtonRow = styled(RowFixed)`
gap: 8px;
${({ theme }) => theme.mediaWidth.upToSmall`
width: 100%;
flex-direction: row-reverse;
justify-content: space-between;
`};
`
const ResponsiveButtonPrimary = styled(ButtonPrimary)`
width: fit-content;
${({ theme }) => theme.mediaWidth.upToSmall`
width: 48%;
`};
`
const ResponsiveButtonSecondary = styled(ButtonSecondary)`
width: fit-content;
${({ theme }) => theme.mediaWidth.upToSmall`
width: 48%;
`};
`
const EmptyProposals = styled.div`
border: 1px solid ${({ theme }) => theme.text4};
padding: 16px 12px;
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`
export default function Pool() {
const theme = useContext(ThemeContext)
const { account } = useActiveWeb3React()
// fetch the user's balances of all tracked V2 LP tokens
const trackedTokenPairs = useTrackedTokenPairs()
const tokenPairsWithLiquidityTokens = useMemo(
() => trackedTokenPairs.map((tokens) => ({ liquidityToken: toV2LiquidityToken(tokens), tokens })),
[trackedTokenPairs]
)
const liquidityTokens = useMemo(() => tokenPairsWithLiquidityTokens.map((tpwlt) => tpwlt.liquidityToken), [
tokenPairsWithLiquidityTokens,
])
const [v2PairsBalances, fetchingV2PairBalances] = useTokenBalancesWithLoadingIndicator(
account ?? undefined,
liquidityTokens
)
// fetch the reserves for all V2 pools in which the user has a balance
const liquidityTokensWithBalances = useMemo(
() =>
tokenPairsWithLiquidityTokens.filter(({ liquidityToken }) =>
v2PairsBalances[liquidityToken.address]?.greaterThan('0')
),
[tokenPairsWithLiquidityTokens, v2PairsBalances]
)
const v2Pairs = usePairs(liquidityTokensWithBalances.map(({ tokens }) => tokens))
const v2IsLoading =
fetchingV2PairBalances || v2Pairs?.length < liquidityTokensWithBalances.length || v2Pairs?.some((V2Pair) => !V2Pair)
const allV2PairsWithLiquidity = v2Pairs.map(([, pair]) => pair).filter((v2Pair): v2Pair is Pair => Boolean(v2Pair))
const hasV1Liquidity = useUserHasLiquidityInAllTokens()
// show liquidity even if its deposited in rewards contract
const stakingInfo = useStakingInfo()
const stakingInfosWithBalance = stakingInfo?.filter((pool) => JSBI.greaterThan(pool.stakedAmount.raw, BIG_INT_ZERO))
const stakingPairs = usePairs(stakingInfosWithBalance?.map((stakingInfo) => stakingInfo.tokens))
// remove any pairs that also are included in pairs with stake in mining pool
const v2PairsWithoutStakedAmount = allV2PairsWithLiquidity.filter((v2Pair) => {
return (
stakingPairs
?.map((stakingPair) => stakingPair[1])
.filter((stakingPair) => stakingPair?.liquidityToken.address === v2Pair.liquidityToken.address).length === 0
)
})
return (
<>
<PageWrapper>
<SwapPoolTabs active={'pool'} />
<VoteCard>
<CardBGImage />
<CardNoise />
<CardSection>
<AutoColumn gap="md">
<RowBetween>
<TYPE.white fontWeight={600}>Liquidity provider rewards</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white fontSize={14}>
{`Liquidity providers earn a 0.3% fee on all trades proportional to their share of the pool. Fees are added to the pool, accrue in real time and can be claimed by withdrawing your liquidity.`}
</TYPE.white>
</RowBetween>
<ExternalLink
style={{ color: 'white', textDecoration: 'underline' }}
target="_blank"
href="https://uniswap.org/docs/v2/core-concepts/pools/"
>
<TYPE.white fontSize={14}>Read more about providing liquidity</TYPE.white>
</ExternalLink>
</AutoColumn>
</CardSection>
<CardBGImage />
<CardNoise />
</VoteCard>
<AutoColumn gap="lg" justify="center">
<AutoColumn gap="lg" style={{ width: '100%' }}>
<TitleRow style={{ marginTop: '1rem' }} padding={'0'}>
<HideSmall>
<TYPE.mediumHeader style={{ marginTop: '0.5rem', justifySelf: 'flex-start' }}>
Your liquidity
</TYPE.mediumHeader>
</HideSmall>
<ButtonRow>
<ResponsiveButtonSecondary as={Link} padding="6px 8px" to="/create/ETH">
Create a pair
</ResponsiveButtonSecondary>
<ResponsiveButtonPrimary
id="join-pool-button"
as={Link}
padding="6px 8px"
borderRadius="12px"
to="/add/ETH"
>
<Text fontWeight={500} fontSize={16}>
Add Liquidity
</Text>
</ResponsiveButtonPrimary>
</ButtonRow>
</TitleRow>
{!account ? (
<Card padding="40px">
<TYPE.body color={theme.text3} textAlign="center">
Connect to a wallet to view your liquidity.
</TYPE.body>
</Card>
) : v2IsLoading ? (
<EmptyProposals>
<TYPE.body color={theme.text3} textAlign="center">
<Dots>Loading</Dots>
</TYPE.body>
</EmptyProposals>
) : allV2PairsWithLiquidity?.length > 0 || stakingPairs?.length > 0 ? (
<>
<ButtonSecondary>
<RowBetween>
<ExternalLink href={'https://uniswap.info/account/' + account}>
Account analytics and accrued fees
</ExternalLink>
<span></span>
</RowBetween>
</ButtonSecondary>
{v2PairsWithoutStakedAmount.map((v2Pair) => (
<FullPositionCard key={v2Pair.liquidityToken.address} pair={v2Pair} />
))}
{stakingPairs.map(
(stakingPair, i) =>
stakingPair[1] && ( // skip pairs that arent loaded
<FullPositionCard
key={stakingInfosWithBalance[i].stakingRewardAddress}
pair={stakingPair[1]}
stakedBalance={stakingInfosWithBalance[i].stakedAmount}
/>
)
)}
</>
) : (
<EmptyProposals>
<TYPE.body color={theme.text3} textAlign="center">
No liquidity found.
</TYPE.body>
</EmptyProposals>
)}
<AutoColumn justify={'center'} gap="md">
<Text textAlign="center" fontSize={14} style={{ padding: '.5rem 0 .5rem 0' }}>
{hasV1Liquidity ? 'Uniswap V1 liquidity found!' : "Don't see a pool you joined?"}{' '}
<StyledInternalLink id="import-pool-link" to={hasV1Liquidity ? '/migrate/v1' : '/find'}>
{hasV1Liquidity ? 'Migrate now.' : 'Import it.'}
</StyledInternalLink>
</Text>
</AutoColumn>
</AutoColumn>
</AutoColumn>
</PageWrapper>
</>
)
}
......@@ -27,6 +27,7 @@ export enum ApplicationModal {
MENU,
DELEGATE,
VOTE,
POOL_OVERVIEW_OPTIONS,
}
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber')
......
import { transparentize } from 'polished'
import React, { useMemo } from 'react'
import { Text, TextProps } from 'rebass'
import styled, {
ThemeProvider as StyledComponentsThemeProvider,
createGlobalStyle,
css,
DefaultTheme,
ThemeProvider as StyledComponentsThemeProvider,
} from 'styled-components'
import { useIsDarkMode } from '../state/user/hooks'
import { Text, TextProps } from 'rebass'
import { Colors } from './styled'
export * from './components'
const MEDIA_WIDTHS = {
export const MEDIA_WIDTHS = {
upToExtraSmall: 500,
upToSmall: 720,
upToMedium: 960,
......@@ -83,6 +82,10 @@ export function colors(darkMode: boolean): Colors {
yellow2: '#F3841E',
blue1: '#2172E5',
error: '#FD4040',
success: '#27AE60',
warning: '#F3B71E',
// dont wanna forget these blue yet
// blue4: darkMode ? '#153d6f70' : '#C4D9F8',
// blue5: darkMode ? '#153d6f70' : '#EBF4FF',
......
......@@ -46,6 +46,10 @@ export interface Colors {
yellow1: Color
yellow2: Color
blue1: Color
error: Color
success: Color
warning: Color
}
export interface Grids {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment