Compare commits

...

9 Commits

Author SHA1 Message Date
Anxo Rodriguez
d9825622f1 Allow absolute imports (#1185) 2020-11-03 09:16:38 -06:00
Nadav Hollander
8975086a69 Remove title parsing from description output (#1179)
Co-authored-by: Nadav Hollander <nadavhollander@Nadavs-Work-MacBook-Pro.local>
2020-10-24 19:40:56 -04:00
Moody Salem
ddf88345a9 fix(token lists): stop showing notifications for lists that are not selected (#1174) 2020-10-20 14:03:51 -05:00
Shane Fontaine
32ac25556b improve displayed proposal end time accuracy (#1173) 2020-10-19 10:14:56 -04:00
Ian Lapham
50a599c005 update vote fetching logic, delegate only on global page (#1165)
* update vote fetching logic, delegate only on global page

* Update index.ts

* add helper message on proposal page

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2020-10-12 17:13:27 -04:00
Ian Lapham
9c473270ee fix(governance): modal bugs, redable styles, show common names for contracts (#1164)
* fix modal bugs, reable styles, show common names for contracts

* use theme for blue
2020-10-12 15:45:20 -04:00
_XiaoTian
b650b17563 fix: Fix the style of account transaction record list. (#1104)
Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
2020-10-09 20:09:33 -05:00
Nikita Kudryavtsev
fc76177791 fix style of proposals text block in vote page (#1149) 2020-10-09 18:12:49 -05:00
Jay Welsh
69655980db fix(ethereum-logo): centers Ethereum logo (#1157) 2020-10-09 10:52:06 -05:00
11 changed files with 141 additions and 75 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -99,7 +99,7 @@ const LowerSection = styled.div`
flex-grow: 1;
overflow: auto;
background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 25px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
h5 {

View File

@@ -20,11 +20,15 @@ export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 18, 'WBTC', 'Wrapped BTC')
// TODO this is only approximate, it's actually based on blocks
export const PROPOSAL_LENGTH_IN_DAYS = 7
// Block time here is slightly higher (~1s) than average in order to avoid ongoing proposals past the displayed time
export const AVERAGE_BLOCK_TIME_IN_SECS = 14
export const PROPOSAL_LENGTH_IN_BLOCKS = 40_320
export const PROPOSAL_LENGTH_IN_SECS = AVERAGE_BLOCK_TIME_IN_SECS * PROPOSAL_LENGTH_IN_BLOCKS
export const GOVERNANCE_ADDRESS = '0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F'
export const TIMELOCK_ADDRESS = '0x1a9C8182C09F50C8318d769245beA52c32BE35BC'
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
export const UNI: { [chainId in ChainId]: Token } = {
[ChainId.MAINNET]: new Token(ChainId.MAINNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
@@ -34,6 +38,12 @@ export const UNI: { [chainId in ChainId]: Token } = {
[ChainId.KOVAN]: new Token(ChainId.KOVAN, UNI_ADDRESS, 18, 'UNI', 'Uniswap')
}
export const COMMON_CONTRACT_NAMES: { [address: string]: string } = {
[UNI_ADDRESS]: 'UNI',
[GOVERNANCE_ADDRESS]: 'Governance',
[TIMELOCK_ADDRESS]: 'Timelock'
}
// TODO: specify merkle distributor for mainnet
export const MERKLE_DISTRIBUTOR_ADDRESS: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '0x090D4613473dEE047c3f2706764f49E0821D256e'

View File

@@ -9,16 +9,20 @@ import { CardSection, DataCard } from '../../components/earn/styled'
import { ArrowLeft } from 'react-feather'
import { ButtonPrimary } from '../../components/Button'
import { ProposalStatus } from './styled'
import { useProposalData, useUserVotes, useUserDelegatee, ProposalData } from '../../state/governance/hooks'
import { useProposalData, useUserVotesAsOfBlock, ProposalData, useUserDelegatee } from '../../state/governance/hooks'
import { useTimestampFromBlock } from '../../hooks/useTimestampFromBlock'
import { DateTime } from 'luxon'
import ReactMarkdown from 'react-markdown'
import VoteModal from '../../components/vote/VoteModal'
import { TokenAmount, JSBI } from '@uniswap/sdk'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useActiveWeb3React } from '../../hooks'
import { UNI, ZERO_ADDRESS, PROPOSAL_LENGTH_IN_DAYS } from '../../constants'
import { PROPOSAL_LENGTH_IN_SECS, COMMON_CONTRACT_NAMES, UNI, ZERO_ADDRESS } from '../../constants'
import { isAddress, getEtherscanLink } from '../../utils'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleDelegateModal, useToggleVoteModal } from '../../state/application/hooks'
import DelegateModal from '../../components/vote/DelegateModal'
import { GreyCard } from '../../components/Card'
import { useTokenBalance } from '../../state/wallet/hooks'
const PageWrapper = styled(AutoColumn)`
width: 100%;
@@ -99,7 +103,7 @@ export default function VotePage({
params: { id }
}
}: RouteComponentProps<{ id: string }>) {
const { account, chainId } = useActiveWeb3React()
const { chainId, account } = useActiveWeb3React()
// get data for this specific proposal
const proposalData: ProposalData | undefined = useProposalData(id)
@@ -108,12 +112,17 @@ export default function VotePage({
const [support, setSupport] = useState<boolean>(true)
// modal for casting votes
const [showModal, setShowModal] = useState<boolean>(false)
const showVoteModal = useModalOpen(ApplicationModal.VOTE)
const toggleVoteModal = useToggleVoteModal()
// toggle for showing delegation modal
const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
const toggelDelegateModal = useToggleDelegateModal()
// get and format date from data
const startTimestamp: number | undefined = useTimestampFromBlock(proposalData?.startBlock)
const endDate: DateTime | undefined = startTimestamp
? DateTime.fromSeconds(startTimestamp).plus({ days: PROPOSAL_LENGTH_IN_DAYS })
? DateTime.fromSeconds(startTimestamp).plus({ seconds: PROPOSAL_LENGTH_IN_SECS })
: undefined
const now: DateTime = DateTime.local()
@@ -124,30 +133,38 @@ export default function VotePage({
const againstPercentage: string =
proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) + '%' : '0%'
// show delegation option if they have have a balance, have not delegated
const availableVotes: TokenAmount | undefined = useUserVotes()
// only count available votes as of the proposal start block
const availableVotes: TokenAmount | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined)
// only show voting if user has > 0 votes at proposal start block and proposal is active,
const showVotingButtons =
availableVotes &&
JSBI.greaterThan(availableVotes.raw, JSBI.BigInt(0)) &&
proposalData &&
proposalData.status === 'active'
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
const userDelegatee: string | undefined = useUserDelegatee()
const showUnlockVoting = Boolean(
// in blurb link to home page if they are able to unlock
const showLinkForUnlock = Boolean(
uniBalance && JSBI.notEqual(uniBalance.raw, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
)
// show links in propsoal details if content is an address
// if content is contract with common name, replace address with common name
const linkIfAddress = (content: string) => {
if (isAddress(content) && chainId) {
return <ExternalLink href={getEtherscanLink(chainId, content, 'address')}>{content}</ExternalLink>
const commonName = COMMON_CONTRACT_NAMES[content] ?? content
return <ExternalLink href={getEtherscanLink(chainId, content, 'address')}>{commonName}</ExternalLink>
}
return <span>{content}</span>
}
return (
<PageWrapper gap="lg" justify="center">
<VoteModal
isOpen={showModal}
onDismiss={() => setShowModal(false)}
proposalId={proposalData?.id}
support={support}
/>
<VoteModal isOpen={showVoteModal} onDismiss={toggleVoteModal} proposalId={proposalData?.id} support={support} />
<DelegateModal isOpen={showDelegateModal} onDismiss={toggelDelegateModal} title="Unlock Votes" />
<ProposalInfo gap="lg" justify="start">
<RowBetween style={{ width: '100%' }}>
<ArrowWrapper to="/vote">
@@ -162,33 +179,32 @@ export default function VotePage({
{endDate && endDate < now
? 'Voting ended ' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
: proposalData
? 'Voting ends approximately' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
? 'Voting ends approximately ' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
: ''}
</TYPE.main>
{showUnlockVoting && endDate && endDate > now && (
<ButtonPrimary
style={{ width: 'fit-content' }}
padding="8px"
borderRadius="8px"
onClick={() => setShowModal(true)}
>
Unlock Voting
</ButtonPrimary>
)}
</RowBetween>
{proposalData && proposalData.status === 'active' && !showVotingButtons && (
<GreyCard>
<TYPE.black>
Only UNI votes that were self delegated or delegated to another address before block{' '}
{proposalData.startBlock} are eligible for voting.{' '}
{showLinkForUnlock && (
<span>
<StyledInternalLink to="/vote">Unlock voting</StyledInternalLink> to prepare for the next proposal.
</span>
)}
</TYPE.black>
</GreyCard>
)}
</AutoColumn>
{!showUnlockVoting &&
availableVotes &&
JSBI.greaterThan(availableVotes?.raw, JSBI.BigInt(0)) &&
endDate &&
endDate > now ? (
{showVotingButtons ? (
<RowFixed style={{ width: '100%', gap: '12px' }}>
<ButtonPrimary
padding="8px"
borderRadius="8px"
onClick={() => {
setSupport(true)
setShowModal(true)
toggleVoteModal()
}}
>
Vote For
@@ -198,7 +214,7 @@ export default function VotePage({
borderRadius="8px"
onClick={() => {
setSupport(false)
setShowModal(true)
toggleVoteModal()
}}
>
Vote Against
@@ -260,7 +276,7 @@ export default function VotePage({
})}
</AutoColumn>
<AutoColumn gap="md">
<TYPE.mediumHeader fontWeight={600}>Overview</TYPE.mediumHeader>
<TYPE.mediumHeader fontWeight={600}>Description</TYPE.mediumHeader>
<MarkDownWrapper>
<ReactMarkdown source={proposalData?.description} />
</MarkDownWrapper>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import { AutoColumn } from '../../components/Column'
import styled from 'styled-components'
import { TYPE, ExternalLink } from '../../theme'
@@ -19,6 +19,8 @@ import { JSBI, TokenAmount, ChainId } from '@uniswap/sdk'
import { shortenAddress, getEtherscanLink } from '../../utils'
import Loader from '../../components/Loader'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import { useModalOpen, useToggleDelegateModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/actions'
const PageWrapper = styled(AutoColumn)``
@@ -102,7 +104,10 @@ const EmptyProposals = styled.div`
export default function Vote() {
const { account, chainId } = useActiveWeb3React()
const [showModal, setShowModal] = useState<boolean>(false)
// toggle for showing delegation modal
const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
const toggelDelegateModal = useToggleDelegateModal()
// get data to list all proposals
const allProposals: ProposalData[] = useAllProposalData()
@@ -120,8 +125,8 @@ export default function Vote() {
return (
<PageWrapper gap="lg" justify="center">
<DelegateModal
isOpen={showModal}
onDismiss={() => setShowModal(false)}
isOpen={showDelegateModal}
onDismiss={toggelDelegateModal}
title={showUnlockVoting ? 'Unlock Votes' : 'Update Delegation'}
/>
<TopSection gap="md">
@@ -154,14 +159,14 @@ export default function Vote() {
</TopSection>
<TopSection gap="2px">
<WrapSmall>
<TYPE.mediumHeader style={{ margin: '0.5rem 0' }}>Proposals</TYPE.mediumHeader>
<TYPE.mediumHeader style={{ margin: '0.5rem 0.5rem 0.5rem 0', flexShrink: 0 }}>Proposals</TYPE.mediumHeader>
{(!allProposals || allProposals.length === 0) && !availableVotes && <Loader />}
{showUnlockVoting ? (
<ButtonPrimary
style={{ width: 'fit-content' }}
padding="8px"
borderRadius="8px"
onClick={() => setShowModal(true)}
onClick={toggelDelegateModal}
>
Unlock Voting
</ButtonPrimary>
@@ -195,7 +200,7 @@ export default function Vote() {
>
{userDelegatee === account ? 'Self' : shortenAddress(userDelegatee)}
</StyledExternalLink>
<TextButton onClick={() => setShowModal(true)} style={{ marginLeft: '4px' }}>
<TextButton onClick={toggelDelegateModal} style={{ marginLeft: '4px' }}>
(edit)
</TextButton>
</AddressButton>

View File

@@ -24,7 +24,9 @@ export enum ApplicationModal {
SELF_CLAIM,
ADDRESS_CLAIM,
CLAIM_POPUP,
MENU
MENU,
DELEGATE,
VOTE
}
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber')

View File

@@ -51,6 +51,14 @@ export function useToggleSelfClaimModal(): () => void {
return useToggleModal(ApplicationModal.SELF_CLAIM)
}
export function useToggleDelegateModal(): () => void {
return useToggleModal(ApplicationModal.DELEGATE)
}
export function useToggleVoteModal(): () => void {
return useToggleModal(ApplicationModal.VOTE)
}
// returns a function that allows adding a popup
export function useAddPopup(): (content: PopupContent, key?: string) => void {
const dispatch = useDispatch()

View File

@@ -124,7 +124,7 @@ export function useAllProposalData() {
const formattedProposal: ProposalData = {
id: allProposals[i]?.result?.id.toString(),
title: formattedEvents[i].description?.split(/# |\n/g)[1] || 'Untitled',
description: formattedEvents[i].description?.split(/# /)[1] || 'No description.',
description: formattedEvents[i].description || 'No description.',
proposer: allProposals[i]?.result?.proposer,
status: enumerateProposalState(allProposalStates[i]?.result?.[0]) ?? 'Undetermined',
forCount: parseFloat(ethers.utils.formatUnits(allProposals[i]?.result?.forVotes.toString(), 18)),
@@ -153,6 +153,7 @@ export function useUserDelegatee(): string {
return result?.[0] ?? undefined
}
// gets the users current votes
export function useUserVotes(): TokenAmount | undefined {
const { account, chainId } = useActiveWeb3React()
const uniContract = useUniContract()
@@ -163,6 +164,18 @@ export function useUserVotes(): TokenAmount | undefined {
return votes && uni ? new TokenAmount(uni, votes) : undefined
}
// fetch available votes as of block (usually proposal start block)
export function useUserVotesAsOfBlock(block: number | undefined): TokenAmount | undefined {
const { account, chainId } = useActiveWeb3React()
const uniContract = useUniContract()
// check for available votes
const uni = chainId ? UNI[chainId] : undefined
const votes = useSingleCallResult(uniContract, 'getPriorVotes', [account ?? undefined, block ?? undefined])
?.result?.[0]
return votes && uni ? new TokenAmount(uni, votes) : undefined
}
export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise<string> {
const { account, chainId, library } = useActiveWeb3React()
const addTransaction = useTransactionAdder()

View File

@@ -13,6 +13,9 @@ export default function Updater(): null {
const { library } = useActiveWeb3React()
const dispatch = useDispatch<AppDispatch>()
const lists = useSelector<AppState, AppState['lists']['byUrl']>(state => state.lists.byUrl)
const selectedListUrl = useSelector<AppState, AppState['lists']['selectedListUrl']>(
state => state.lists.selectedListUrl
)
const isWindowVisible = useIsWindowVisible()
@@ -54,19 +57,21 @@ export default function Updater(): null {
// automatically update minor/patch as long as bump matches the min update
if (bump >= min) {
dispatch(acceptListUpdate(listUrl))
dispatch(
addPopup({
key: listUrl,
content: {
listUpdate: {
listUrl,
oldList: list.current,
newList: list.pendingUpdate,
auto: true
if (listUrl === selectedListUrl) {
dispatch(
addPopup({
key: listUrl,
content: {
listUpdate: {
listUrl,
oldList: list.current,
newList: list.pendingUpdate,
auto: true
}
}
}
})
)
})
)
}
} else {
console.error(
`List at url ${listUrl} could not automatically update because the version bump was only PATCH/MINOR while the update had breaking changes and should have been MAJOR`
@@ -75,24 +80,26 @@ export default function Updater(): null {
break
case VersionUpgrade.MAJOR:
dispatch(
addPopup({
key: listUrl,
content: {
listUpdate: {
listUrl,
auto: false,
oldList: list.current,
newList: list.pendingUpdate
}
},
removeAfterMs: null
})
)
if (listUrl === selectedListUrl) {
dispatch(
addPopup({
key: listUrl,
content: {
listUpdate: {
listUrl,
auto: false,
oldList: list.current,
newList: list.pendingUpdate
}
},
removeAfterMs: null
})
)
}
}
}
})
}, [dispatch, lists])
}, [dispatch, lists, selectedListUrl])
return null
}

View File

@@ -192,6 +192,10 @@ body {
padding: 0;
}
a {
color: ${colors(false).blue1};
}
* {
box-sizing: border-box;
}

View File

@@ -22,7 +22,8 @@
"jsx": "preserve",
"downlevelIteration": true,
"allowSyntheticDefaultImports": true,
"types": ["react-spring", "jest"]
"types": ["react-spring", "jest"],
"baseUrl": "src"
},
"exclude": ["node_modules", "cypress"],
"include": ["./src/**/*.ts", "./src/**/*.tsx", "src/components/Confetti/index.js"]