Skip to content

Commit

Permalink
Merge pull request #1177 from AutarkLabs/1114-dotvoting-aragonDS
Browse files Browse the repository at this point in the history
1114 dotvoting aragon ds
  • Loading branch information
Chad Ostrowski committed Aug 27, 2019
2 parents ef305ab + a91956f commit b640cf7
Show file tree
Hide file tree
Showing 36 changed files with 1,639 additions and 1,617 deletions.
2 changes: 1 addition & 1 deletion apps/address-book/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"@aragon/api": "2.0.0-beta.5",
"@aragon/api-react": "2.0.0-beta.4",
"@aragon/ui": "0.33.0",
"@aragon/ui": "1.0.0-alpha.9",
"@tps/ui": "^0.0.1",
"prop-types": "^15.7.2",
"react": "^16.8.6",
Expand Down
2 changes: 1 addition & 1 deletion apps/allocations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@aragon/api": "2.0.0-beta.5",
"@aragon/api-react": "2.0.0-beta.4",
"@aragon/apps-vault": "^4.1.0",
"@aragon/ui": "0.33.0",
"@aragon/ui": "1.0.0-alpha.9",
"@babel/polyfill": "^7.2.5",
"@tps/apps-address-book": "^0.0.1",
"@tps/ui": "^0.0.1",
Expand Down
312 changes: 123 additions & 189 deletions apps/dot-voting/app/App.js
Original file line number Diff line number Diff line change
@@ -1,201 +1,135 @@
import React from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { AppBar, AppView, Main, SidePanel } from '@aragon/ui'
import Decisions from './Decisions'
import tokenBalanceOfAbi from './abi/token-balanceof.json'
import tokenDecimalsAbi from './abi/token-decimals.json'
import tokenSymbolAbi from './abi/token-symbol.json'
import { hasLoadedVoteSettings } from './utils/vote-settings'
import { NewPayoutVotePanelContent } from './components/Panels'
import { VotePanelContent } from './components/Panels'
import { AppTitle, networkContextType } from '../../../shared/ui'
import { ASSETS_URL, EmptyStateCard, Header, Main } from '@aragon/ui'
import { useAragonApi } from '@aragon/api-react'
import { isBefore } from 'date-fns'
import { getQuorumProgress, getTotalSupport } from './utils/vote-utils'
import { safeDiv } from './utils/math-utils'
import { IdentityProvider } from '../../../shared/identity'
import Decisions from './Decisions'
import emptyStatePng from './assets/voting-empty-state.png'

const tokenAbi = [].concat(tokenBalanceOfAbi, tokenDecimalsAbi, tokenSymbolAbi)

const initialState = {
template: null,
templateData: {},
stepIndex: 0,
settingsLoaded: false,
panelActive: false,
currentVoteId: -1,
currentVote: null,
voteSidebarOpened: false,
}
const illustration = <img src={emptyStatePng} alt="" height="160" />

class App extends React.Component {
static propTypes = {
api: PropTypes.object,
displayMenuButton: PropTypes.bool.isRequired,
votes: PropTypes.arrayOf(PropTypes.object).isRequired,
entries: PropTypes.arrayOf(PropTypes.object).isRequired,
connectedAccount: PropTypes.string.isRequired,
network: PropTypes.object,
tokenAddress: PropTypes.string.isRequired,
minParticipationPct: PropTypes.number.isRequired,
pctBase: PropTypes.number.isRequired,
voteTime: PropTypes.number.isRequired,
}

static defaultProps = {
network: {},
votes: [],
entries: [],
tokenAddress: '',
voteTime: 0,
minParticipationPct: 0,
pctBase: 0,
}

static childContextTypes = {
network: networkContextType,
}

constructor(props) {
super(props)
this.state = {
...initialState,
tokenContract: this.getTokenContract(this.props.tokenAddress),
}
}
const useVoteCloseWatcher = () => {
const { votes = [], voteTime = 0 } = useAragonApi().appState
const [ now, setNow ] = useState(new Date().getTime())

getChildContext() {
const { network } = this.props
return {
network: {
type: network.type,
},
}
}

componentWillReceiveProps(nextProps) {
const { settingsLoaded } = this.state
// Is this the first time we've loaded the settings?
if (!settingsLoaded && hasLoadedVoteSettings(nextProps)) {
this.setState({
settingsLoaded: true,
})
}
if (nextProps.tokenAddress !== this.props.tokenAddress) {
this.setState({
tokenContract: this.getTokenContract(nextProps.tokenAddress),
})
useEffect(() => {
const timeouts = {}

votes.forEach(({ voteId: id, data: { startDate } }) => {
const endTime = new Date(startDate + voteTime).getTime()

if (endTime < now) return // ignore; voting has closed

timeouts[id] = setTimeout(
() => setNow(new Date().getTime()),
endTime - now
)
})

return function cleanup() {
for (let id in timeouts) {
clearTimeout(timeouts[id])
}
}
}

getTokenContract(tokenAddress) {
return tokenAddress && this.props.api.external(tokenAddress, tokenAbi)
}
handlePanelOpen = () => {
this.setState({ panelActive: true })
}

handlePanelClose = () => {
this.setState({ panelActive: false })
}

handleResolveLocalIdentity = address => {
return this.props.api.resolveAddressIdentity(address).toPromise()
}

handleShowLocalIdentityModal = address => {
return this.props.api
}, [ votes, voteTime ])
}

const Wrap = ({ children }) => (
<Main assetsUrl={ASSETS_URL}>
<Header primary="Dot Voting" />
{children}
</Main>
)

Wrap.propTypes = {
children: PropTypes.node.isRequired,
}

const Empty = () => (
<Wrap>
<div
css={`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
`}
>
<EmptyStateCard
title="You do not have any dot votes."
text="Use the Allocations app to get started."
onActivate={() => <div />}
illustration={illustration}
/>
</div>
</Wrap>
)

const App = () => {
useVoteCloseWatcher()

const { api, appState = {} } = useAragonApi()

const handleResolveLocalIdentity = useCallback(address => {
return api.resolveAddressIdentity(address).toPromise()
}, [api])

const handleShowLocalIdentityModal = useCallback(address => {
return api
.requestAddressIdentityModification(address)
.toPromise()
}

handleVoteOpen = selectedVote => {
this.setState({
currentVoteId: selectedVote.voteId,
currentVote: selectedVote,
voteVisible: true,
voteSidebarOpened: false,
})
}

handleVote = (voteId, supports) => {
this.props.api.vote(voteId, supports).toPromise()
this.handleVoteClose()
}
handleVoteTransitionEnd = opened => {
this.setState(opened ? { voteSidebarOpened: true } : { currentVoteId: -1, currentVote: null })
}

handleVoteClose = () => {
this.setState({
currentVoteId: -1,
currentVote: null,
})
}

render() {
const { displayMenuButton = false } = this.props
return (
<Main>
<IdentityProvider
onResolve={this.handleResolveLocalIdentity}
onShowLocalIdentityModal={this.handleShowLocalIdentityModal}>
<AppView
appBar={
<AppBar>
<AppTitle
title="Dot Voting"
displayMenuButton={displayMenuButton}
css="padding-left: 30px"
/>
</AppBar>
}
>
<Decisions
onActivate={this.handlePanelOpen}
app={this.props.api}
votes={this.props.votes !== undefined ? this.props.votes : []}
entries={this.props.entries !== undefined ? this.props.entries : []}
voteTime={this.props.voteTime}
minParticipationPct={this.props.minParticipationPct / 10 ** 16}
pctBase={this.props.pctBase / 10 ** 16}
tokenAddress={this.props.tokenAddress}
userAccount={this.props.connectedAccount}
onSelectVote={this.handleVoteOpen}
/>
</AppView>

<SidePanel
title={''}
opened={this.state.panelActive}
onClose={this.handlePanelClose}
>
<NewPayoutVotePanelContent />
</SidePanel>

{this.state.currentVote && (<SidePanel
title={'Dot Vote #' + this.state.currentVoteId}
opened={!!this.state.currentVote}
onClose={this.handleVoteClose}
onTransitionEnd={this.handleVoteTransitionEnd}
>
<VotePanelContent
app={this.props.api}
vote={this.state.currentVote}
user={this.props.connectedAccount}
ready={this.state.voteSidebarOpened}
tokenContract={this.state.tokenContract}
onVote={this.handleVote}
minParticipationPct={this.props.minParticipationPct / 10 ** 16}
/>
</SidePanel>
)}

</IdentityProvider>
</Main>
)
}
}, [api])

const {
votes = [],
entries = [],
voteTime = 0,
minParticipationPct = 0,
pctBase = 0,
} = appState

const getAddressLabel = useCallback(option => {
const entry = entries.find(entry => entry.addr === option.label)
return entry ? entry.data.name : option.label
}, [entries])

// TODO: move this logic to script.js so it's available app-wide by default
const decorateVote = useCallback(vote => {
const endDate = new Date(vote.data.startDate + voteTime)
vote.data.options = vote.data.options.map(option => ({
...option,
label: getAddressLabel(option)
}))
return {
...vote,
endDate,
open: isBefore(new Date(), endDate),
quorum: safeDiv(vote.data.minAcceptQuorum, pctBase),
quorumProgress: getQuorumProgress(vote.data),
minParticipationPct: minParticipationPct / 10 ** 16,
description: vote.data.metadata,
totalSupport: getTotalSupport(vote.data),
type: vote.data.type,
}
}, [ voteTime, getAddressLabel, pctBase, minParticipationPct ])

if (!votes.length) return <Empty />

return (
<Wrap>
<IdentityProvider
onResolve={handleResolveLocalIdentity}
onShowLocalIdentityModal={handleShowLocalIdentityModal}>

<Decisions decorateVote={decorateVote} />
</IdentityProvider>
</Wrap>
)
}

// eslint-disable-next-line react/display-name
export default () => {
const { api, appState, connectedAccount, displayMenuButton } = useAragonApi()
return <App api={api} {...appState} connectedAccount={connectedAccount} displayMenuButton={displayMenuButton} />
}
export default App
Loading

0 comments on commit b640cf7

Please sign in to comment.