perf: lazy load vote related routes (#2468)

* perf: lazy load vote related routes

* wrap Switch in Suspense

* remove exact to match nested routes

* fix nested routes

* split Landing

* fix
This commit is contained in:
Sam Chen 2021-11-18 14:01:45 +08:00 committed by GitHub
parent 0f35f6ee93
commit 222a6d53bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 310 additions and 286 deletions

@ -1,5 +1,7 @@
import Loader from 'components/Loader'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
import { Route, Switch } from 'react-router-dom' import { lazy, Suspense } from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter' import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
@ -15,7 +17,6 @@ import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import AddLiquidity from './AddLiquidity'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects' import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects' import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
import CreateProposal from './CreateProposal'
import Earn from './Earn' import Earn from './Earn'
import Manage from './Earn/Manage' import Manage from './Earn/Manage'
import MigrateV2 from './MigrateV2' import MigrateV2 from './MigrateV2'
@ -28,8 +29,8 @@ import RemoveLiquidity from './RemoveLiquidity'
import RemoveLiquidityV3 from './RemoveLiquidity/V3' import RemoveLiquidityV3 from './RemoveLiquidity/V3'
import Swap from './Swap' import Swap from './Swap'
import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects' import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
import Vote from './Vote'
import VotePage from './Vote/VotePage' const Vote = lazy(() => import('./Vote'))
const AppWrapper = styled.div` const AppWrapper = styled.div`
display: flex; display: flex;
@ -85,46 +86,54 @@ export default function App() {
<Popups /> <Popups />
<Polling /> <Polling />
<TopLevelModals /> <TopLevelModals />
<Switch> <Suspense fallback={<Loader />}>
<Route exact strict path="/vote" component={Vote} /> <Switch>
<Route exact strict path="/vote/:governorIndex/:id" component={VotePage} /> <Route strict path="/vote" component={Vote} />
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} /> <Route exact strict path="/create-proposal">
<Route exact strict path="/uni" component={Earn} /> <Redirect to="/vote/create-proposal" />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} /> </Route>
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} /> <Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} /> <Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/swap" component={Swap} /> <Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/pool/v2/find" component={PoolFinder} /> <Route exact strict path="/pool/v2/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} /> <Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} /> <Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} /> <Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} /> <Route
<Route exact
exact strict
strict path="/add/v2/:currencyIdA?/:currencyIdB?"
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?" component={RedirectDuplicateTokenIdsV2}
component={RedirectDuplicateTokenIds} />
/> <Route
exact
strict
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIds}
/>
<Route <Route
exact exact
strict strict
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?" path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity} component={AddLiquidity}
/> />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} /> <Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} /> <Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
<Route exact strict path="/migrate/v2" component={MigrateV2} /> <Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} /> <Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
<Route exact strict path="/create-proposal" component={CreateProposal} /> <Route component={RedirectPathToSwapOnly} />
<Route component={RedirectPathToSwapOnly} /> </Switch>
</Switch> </Suspense>
<Marginer /> <Marginer />
</BodyWrapper> </BodyWrapper>
</AppWrapper> </AppWrapper>

257
src/pages/Vote/Landing.tsx Normal file

@ -0,0 +1,257 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import { CardBGImage, CardNoise, CardSection, DataCard } from 'components/earn/styled'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import Loader from 'components/Loader'
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import DelegateModal from 'components/vote/DelegateModal'
import ProposalEmptyState from 'components/vote/ProposalEmptyState'
import { useActiveWeb3React } from 'hooks/web3'
import JSBI from 'jsbi'
import { darken } from 'polished'
import { Link } from 'react-router-dom'
import { Button } from 'rebass/styled-components'
import { useModalOpen, useToggleDelegateModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { ProposalData } from 'state/governance/hooks'
import { useAllProposalData, useUserDelegatee, useUserVotes } from 'state/governance/hooks'
import { useTokenBalance } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, TYPE } from 'theme'
import { shortenAddress } from 'utils'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import { ZERO_ADDRESS } from '../../constants/misc'
import { UNI } from '../../constants/tokens'
import { ProposalStatus } from './styled'
const PageWrapper = styled(AutoColumn)``
const TopSection = styled(AutoColumn)`
max-width: 640px;
width: 100%;
`
const Proposal = styled(Button)`
padding: 0.75rem 1rem;
width: 100%;
margin-top: 1rem;
border-radius: 12px;
display: grid;
grid-template-columns: 48px 1fr 120px;
align-items: center;
text-align: left;
outline: none;
cursor: pointer;
color: ${({ theme }) => theme.text1};
text-decoration: none;
background-color: ${({ theme }) => theme.bg1};
&:focus {
background-color: ${({ theme }) => darken(0.05, theme.bg1)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.bg1)};
}
`
const ProposalNumber = styled.span`
opacity: 0.6;
`
const ProposalTitle = styled.span`
font-weight: 600;
`
const VoteCard = styled(DataCard)`
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #27ae60 0%, #000000 100%);
overflow: hidden;
`
const WrapSmall = styled(RowBetween)`
margin-bottom: 1rem;
${({ theme }) => theme.mediaWidth.upToSmall`
flex-wrap: wrap;
`};
`
const TextButton = styled(TYPE.main)`
color: ${({ theme }) => theme.primary1};
:hover {
cursor: pointer;
text-decoration: underline;
}
`
const AddressButton = styled.div`
border: 1px solid ${({ theme }) => theme.bg3};
padding: 2px 4px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
`
const StyledExternalLink = styled(ExternalLink)`
color: ${({ theme }) => theme.text1};
`
export default function Landing() {
const { account, chainId } = useActiveWeb3React()
// toggle for showing delegation modal
const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
const toggleDelegateModal = useToggleDelegateModal()
// get data to list all proposals
const { data: allProposals, loading: loadingProposals } = useAllProposalData()
// user data
const { loading: loadingAvailableVotes, votes: availableVotes } = useUserVotes()
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(
account ?? undefined,
chainId ? UNI[chainId] : undefined
)
const userDelegatee: string | undefined = useUserDelegatee()
// show delegation option if they have have a balance, but have not delegated
const showUnlockVoting = Boolean(
uniBalance && JSBI.notEqual(uniBalance.quotient, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
)
return (
<>
<PageWrapper gap="lg" justify="center">
<DelegateModal
isOpen={showDelegateModal}
onDismiss={toggleDelegateModal}
title={showUnlockVoting ? <Trans>Unlock Votes</Trans> : <Trans>Update Delegation</Trans>}
/>
<TopSection gap="md">
<VoteCard>
<CardBGImage />
<CardNoise />
<CardSection>
<AutoColumn gap="md">
<RowBetween>
<TYPE.white fontWeight={600}>
<Trans>Uniswap Governance</Trans>
</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white fontSize={14}>
<Trans>
UNI tokens represent voting shares in Uniswap governance. You can vote on each proposal yourself
or delegate your votes to a third party.
</Trans>
</TYPE.white>
</RowBetween>
<ExternalLink
style={{ color: 'white', textDecoration: 'underline' }}
href="https://uniswap.org/blog/uni"
target="_blank"
>
<TYPE.white fontSize={14}>
<Trans>Read more about Uniswap governance</Trans>
</TYPE.white>
</ExternalLink>
</AutoColumn>
</CardSection>
<CardBGImage />
<CardNoise />
</VoteCard>
</TopSection>
<TopSection gap="2px">
<WrapSmall>
<TYPE.mediumHeader style={{ margin: '0.5rem 0.5rem 0.5rem 0', flexShrink: 0 }}>
<Trans>Proposals</Trans>
</TYPE.mediumHeader>
<AutoRow gap="6px" justify="flex-end">
{loadingProposals || loadingAvailableVotes ? <Loader /> : null}
{showUnlockVoting ? (
<ButtonPrimary
style={{ width: 'fit-content' }}
padding="8px"
$borderRadius="8px"
onClick={toggleDelegateModal}
>
<Trans>Unlock Voting</Trans>
</ButtonPrimary>
) : availableVotes && JSBI.notEqual(JSBI.BigInt(0), availableVotes?.quotient) ? (
<TYPE.body fontWeight={500} mr="6px">
<Trans>
<FormattedCurrencyAmount currencyAmount={availableVotes} /> Votes
</Trans>
</TYPE.body>
) : uniBalance &&
userDelegatee &&
userDelegatee !== ZERO_ADDRESS &&
JSBI.notEqual(JSBI.BigInt(0), uniBalance?.quotient) ? (
<TYPE.body fontWeight={500} mr="6px">
<Trans>
<FormattedCurrencyAmount currencyAmount={uniBalance} /> Votes
</Trans>
</TYPE.body>
) : (
''
)}
<ButtonPrimary
as={Link}
to="/create-proposal"
style={{ width: 'fit-content', borderRadius: '8px' }}
padding="8px"
>
<Trans>Create Proposal</Trans>
</ButtonPrimary>
</AutoRow>
</WrapSmall>
{!showUnlockVoting && (
<RowBetween>
<div />
{userDelegatee && userDelegatee !== ZERO_ADDRESS ? (
<RowFixed>
<TYPE.body fontWeight={500} mr="4px">
<Trans>Delegated to:</Trans>
</TYPE.body>
<AddressButton>
<StyledExternalLink
href={getExplorerLink(1, userDelegatee, ExplorerDataType.ADDRESS)}
style={{ margin: '0 4px' }}
>
{userDelegatee === account ? <Trans>Self</Trans> : shortenAddress(userDelegatee)}
</StyledExternalLink>
<TextButton onClick={toggleDelegateModal} style={{ marginLeft: '4px' }}>
<Trans>(edit)</Trans>
</TextButton>
</AddressButton>
</RowFixed>
) : (
''
)}
</RowBetween>
)}
{allProposals?.length === 0 && <ProposalEmptyState />}
{allProposals
?.slice(0)
?.reverse()
?.map((p: ProposalData) => {
return (
<Proposal as={Link} to={`/vote/${p.governorIndex}/${p.id}`} key={`${p.governorIndex}${p.id}`}>
<ProposalNumber>
{p.governorIndex}.{p.id}
</ProposalNumber>
<ProposalTitle>{p.title}</ProposalTitle>
<ProposalStatus status={p.status} />
</Proposal>
)
})}
</TopSection>
<TYPE.subHeader color="text3">
<Trans>A minimum threshold of 0.25% of the total UNI supply is required to submit proposals</Trans>
</TYPE.subHeader>
</PageWrapper>
<SwitchLocaleLink />
</>
)
}

@ -1,257 +1,15 @@
import { Trans } from '@lingui/macro' import CreateProposal from 'pages/CreateProposal'
import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { Route } from 'react-router-dom'
import { ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import { CardBGImage, CardNoise, CardSection, DataCard } from 'components/earn/styled'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import Loader from 'components/Loader'
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import DelegateModal from 'components/vote/DelegateModal'
import ProposalEmptyState from 'components/vote/ProposalEmptyState'
import { useActiveWeb3React } from 'hooks/web3'
import JSBI from 'jsbi'
import { darken } from 'polished'
import { Link } from 'react-router-dom'
import { Button } from 'rebass/styled-components'
import { useModalOpen, useToggleDelegateModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { ProposalData, useAllProposalData, useUserDelegatee, useUserVotes } from 'state/governance/hooks'
import { useTokenBalance } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, TYPE } from 'theme'
import { shortenAddress } from 'utils'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import { ZERO_ADDRESS } from '../../constants/misc' import Landing from './Landing'
import { UNI } from '../../constants/tokens' import VotePage from './VotePage'
import { ProposalStatus } from './styled'
const PageWrapper = styled(AutoColumn)``
const TopSection = styled(AutoColumn)`
max-width: 640px;
width: 100%;
`
const Proposal = styled(Button)`
padding: 0.75rem 1rem;
width: 100%;
margin-top: 1rem;
border-radius: 12px;
display: grid;
grid-template-columns: 48px 1fr 120px;
align-items: center;
text-align: left;
outline: none;
cursor: pointer;
color: ${({ theme }) => theme.text1};
text-decoration: none;
background-color: ${({ theme }) => theme.bg1};
&:focus {
background-color: ${({ theme }) => darken(0.05, theme.bg1)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.bg1)};
}
`
const ProposalNumber = styled.span`
opacity: 0.6;
`
const ProposalTitle = styled.span`
font-weight: 600;
`
const VoteCard = styled(DataCard)`
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #27ae60 0%, #000000 100%);
overflow: hidden;
`
const WrapSmall = styled(RowBetween)`
margin-bottom: 1rem;
${({ theme }) => theme.mediaWidth.upToSmall`
flex-wrap: wrap;
`};
`
const TextButton = styled(TYPE.main)`
color: ${({ theme }) => theme.primary1};
:hover {
cursor: pointer;
text-decoration: underline;
}
`
const AddressButton = styled.div`
border: 1px solid ${({ theme }) => theme.bg3};
padding: 2px 4px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
`
const StyledExternalLink = styled(ExternalLink)`
color: ${({ theme }) => theme.text1};
`
export default function Vote() { export default function Vote() {
const { account, chainId } = useActiveWeb3React()
// toggle for showing delegation modal
const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
const toggleDelegateModal = useToggleDelegateModal()
// get data to list all proposals
const { data: allProposals, loading: loadingProposals } = useAllProposalData()
// user data
const { loading: loadingAvailableVotes, votes: availableVotes } = useUserVotes()
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(
account ?? undefined,
chainId ? UNI[chainId] : undefined
)
const userDelegatee: string | undefined = useUserDelegatee()
// show delegation option if they have have a balance, but have not delegated
const showUnlockVoting = Boolean(
uniBalance && JSBI.notEqual(uniBalance.quotient, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
)
return ( return (
<> <>
<PageWrapper gap="lg" justify="center"> <Route exact strict path="/vote/:governorIndex/:id" component={VotePage} />
<DelegateModal <Route exact strict path="/vote/create-proposal" component={CreateProposal} />
isOpen={showDelegateModal} <Route exact strict path="/vote" component={Landing} />
onDismiss={toggleDelegateModal}
title={showUnlockVoting ? <Trans>Unlock Votes</Trans> : <Trans>Update Delegation</Trans>}
/>
<TopSection gap="md">
<VoteCard>
<CardBGImage />
<CardNoise />
<CardSection>
<AutoColumn gap="md">
<RowBetween>
<TYPE.white fontWeight={600}>
<Trans>Uniswap Governance</Trans>
</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white fontSize={14}>
<Trans>
UNI tokens represent voting shares in Uniswap governance. You can vote on each proposal yourself
or delegate your votes to a third party.
</Trans>
</TYPE.white>
</RowBetween>
<ExternalLink
style={{ color: 'white', textDecoration: 'underline' }}
href="https://uniswap.org/blog/uni"
target="_blank"
>
<TYPE.white fontSize={14}>
<Trans>Read more about Uniswap governance</Trans>
</TYPE.white>
</ExternalLink>
</AutoColumn>
</CardSection>
<CardBGImage />
<CardNoise />
</VoteCard>
</TopSection>
<TopSection gap="2px">
<WrapSmall>
<TYPE.mediumHeader style={{ margin: '0.5rem 0.5rem 0.5rem 0', flexShrink: 0 }}>
<Trans>Proposals</Trans>
</TYPE.mediumHeader>
<AutoRow gap="6px" justify="flex-end">
{loadingProposals || loadingAvailableVotes ? <Loader /> : null}
{showUnlockVoting ? (
<ButtonPrimary
style={{ width: 'fit-content' }}
padding="8px"
$borderRadius="8px"
onClick={toggleDelegateModal}
>
<Trans>Unlock Voting</Trans>
</ButtonPrimary>
) : availableVotes && JSBI.notEqual(JSBI.BigInt(0), availableVotes?.quotient) ? (
<TYPE.body fontWeight={500} mr="6px">
<Trans>
<FormattedCurrencyAmount currencyAmount={availableVotes} /> Votes
</Trans>
</TYPE.body>
) : uniBalance &&
userDelegatee &&
userDelegatee !== ZERO_ADDRESS &&
JSBI.notEqual(JSBI.BigInt(0), uniBalance?.quotient) ? (
<TYPE.body fontWeight={500} mr="6px">
<Trans>
<FormattedCurrencyAmount currencyAmount={uniBalance} /> Votes
</Trans>
</TYPE.body>
) : (
''
)}
<ButtonPrimary
as={Link}
to="/create-proposal"
style={{ width: 'fit-content', borderRadius: '8px' }}
padding="8px"
>
<Trans>Create Proposal</Trans>
</ButtonPrimary>
</AutoRow>
</WrapSmall>
{!showUnlockVoting && (
<RowBetween>
<div />
{userDelegatee && userDelegatee !== ZERO_ADDRESS ? (
<RowFixed>
<TYPE.body fontWeight={500} mr="4px">
<Trans>Delegated to:</Trans>
</TYPE.body>
<AddressButton>
<StyledExternalLink
href={getExplorerLink(1, userDelegatee, ExplorerDataType.ADDRESS)}
style={{ margin: '0 4px' }}
>
{userDelegatee === account ? <Trans>Self</Trans> : shortenAddress(userDelegatee)}
</StyledExternalLink>
<TextButton onClick={toggleDelegateModal} style={{ marginLeft: '4px' }}>
<Trans>(edit)</Trans>
</TextButton>
</AddressButton>
</RowFixed>
) : (
''
)}
</RowBetween>
)}
{allProposals?.length === 0 && <ProposalEmptyState />}
{allProposals
?.slice(0)
?.reverse()
?.map((p: ProposalData) => {
return (
<Proposal as={Link} to={`/vote/${p.governorIndex}/${p.id}`} key={`${p.governorIndex}${p.id}`}>
<ProposalNumber>
{p.governorIndex}.{p.id}
</ProposalNumber>
<ProposalTitle>{p.title}</ProposalTitle>
<ProposalStatus status={p.status} />
</Proposal>
)
})}
</TopSection>
<TYPE.subHeader color="text3">
<Trans>A minimum threshold of 0.25% of the total UNI supply is required to submit proposals</Trans>
</TYPE.subHeader>
</PageWrapper>
<SwitchLocaleLink />
</> </>
) )
} }