Skip to content

Commit

Permalink
Voting: Local Identities (#728)
Browse files Browse the repository at this point in the history
* Voting: upgrade packages for local identities

* Add Identity and modal provider

* Voting: use LocalIdentityBadge

* LocalIdentityBadge: add lower case to check

* Version bump aragon/ui

* Upgrade aragon/api

* Make useEffect hook dependency explicit

* Abstract identity logic with a custom useIdentity react hook (#743)

* Move observable logic into a hook

* Combine identity contexts and define a custom hook

* Add missing propType

* Encapsulate both generation and handling of updates in hook

* Fix code formatting

* Default to null for when an identity is removed

* Handle case where identity resolves to null

* Use ternary operator

* Change order of nesting to have Main first

* Rename address to label

* propagate promise rejection

* Rename address to entity for consistency

* LocalIdentityBadge: fix var name error
  • Loading branch information
2color authored Mar 29, 2019
1 parent a76281f commit 59a024f
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 56 deletions.
8 changes: 4 additions & 4 deletions apps/voting/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"private": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
"@aragon/api": "^1.0.0-beta.1",
"@aragon/ui": "^0.31.0",
"@aragon/api": "^1.0.0",
"@aragon/ui": "^0.33.0",
"bn.js": "^4.11.8",
"date-fns": "2.0.0-alpha.22",
"onecolor": "^3.1.0",
"prop-types": "^15.6.0",
"react": "^16.5.2",
"react-dom": "^16.2.0",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-linkify": "^0.2.2",
"react-spring": "^5.7.2",
"rxjs": "^6.2.1",
Expand Down
117 changes: 67 additions & 50 deletions apps/voting/app/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import NewVotePanelContent from './components/NewVotePanelContent'
import AutoLink from './components/AutoLink'
import AppLayout from './components/AppLayout'
import NewVoteIcon from './components/NewVoteIcon'
import { IdentityProvider } from './components/IdentityManager/IdentityManager'
import { networkContextType } from './utils/provideNetwork'
import { settingsContextType } from './utils/provideSettings'
import { hasLoadedVoteSettings } from './vote-settings'
Expand Down Expand Up @@ -142,6 +143,15 @@ class App extends React.Component {
this.props.sendMessageToWrapper('menuPanel', true)
}

handleResolveLocalIdentity = address => {
return this.props.app.resolveAddressIdentity(address).toPromise()
}
handleShowLocalIdentityModal = address => {
return this.props.app
.requestAddressIdentityModification(address)
.toPromise()
}

shortenAddresses(label) {
return transformAddresses(label, (part, isAddress, index) =>
isAddress ? (
Expand Down Expand Up @@ -212,60 +222,67 @@ class App extends React.Component {
? null
: preparedVotes.find(vote => vote.voteId === currentVoteId)
const hasCurrentVote = appStateReady && Boolean(currentVote)

return (
<div css="min-width: 320px">
<Main assetsUrl="./aragon-ui">
<AppLayout
title="Voting"
onMenuOpen={this.handleMenuPanelOpen}
mainButton={{
label: 'New vote',
icon: <NewVoteIcon />,
onClick: this.handleCreateVoteOpen,
}}
<Main assetsUrl="./aragon-ui">
<div css="min-width: 320px">
<IdentityProvider
onResolve={this.handleResolveLocalIdentity}
onShowLocalIdentityModal={this.handleShowLocalIdentityModal}
>
{appStateReady && votes.length > 0 ? (
<Votes votes={preparedVotes} onSelectVote={this.handleVoteOpen} />
) : (
<EmptyState onActivate={this.handleCreateVoteOpen} />
)}
</AppLayout>
<SidePanel
title={`Vote #${currentVoteId} (${
currentVote && currentVote.data.open ? 'Open' : 'Closed'
})`}
opened={hasCurrentVote && !createVoteVisible && voteVisible}
onClose={this.handleVoteClose}
onTransitionEnd={this.handleVoteTransitionEnd}
>
{hasCurrentVote && (
<VotePanelContent
app={app}
vote={currentVote}
user={userAccount}
ready={voteSidebarOpened}
tokenContract={tokenContract}
tokenDecimals={tokenDecimals}
tokenSymbol={tokenSymbol}
onVote={this.handleVote}
onExecute={this.handleExecute}
/>
)}
</SidePanel>
<AppLayout
title="Voting"
onMenuOpen={this.handleMenuPanelOpen}
mainButton={{
label: 'New vote',
icon: <NewVoteIcon />,
onClick: this.handleCreateVoteOpen,
}}
>
{appStateReady && votes.length > 0 ? (
<Votes
votes={preparedVotes}
onSelectVote={this.handleVoteOpen}
/>
) : (
<EmptyState onActivate={this.handleCreateVoteOpen} />
)}
</AppLayout>
<SidePanel
title={`Vote #${currentVoteId} (${
currentVote && currentVote.data.open ? 'Open' : 'Closed'
})`}
opened={hasCurrentVote && !createVoteVisible && voteVisible}
onClose={this.handleVoteClose}
onTransitionEnd={this.handleVoteTransitionEnd}
>
{hasCurrentVote && (
<VotePanelContent
app={app}
vote={currentVote}
user={userAccount}
ready={voteSidebarOpened}
tokenContract={tokenContract}
tokenDecimals={tokenDecimals}
tokenSymbol={tokenSymbol}
onVote={this.handleVote}
onExecute={this.handleExecute}
/>
)}
</SidePanel>

<SidePanel
title="New Vote"
opened={createVoteVisible}
onClose={this.handleCreateVoteClose}
>
<NewVotePanelContent
<SidePanel
title="New Vote"
opened={createVoteVisible}
onCreateVote={this.handleCreateVote}
/>
</SidePanel>
</Main>
</div>
onClose={this.handleCreateVoteClose}
>
<NewVotePanelContent
opened={createVoteVisible}
onCreateVote={this.handleCreateVote}
/>
</SidePanel>
</IdentityProvider>
</div>
</Main>
)
}
}
Expand Down
36 changes: 36 additions & 0 deletions apps/voting/app/src/components/IdentityManager/IdentityManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Subject } from 'rxjs'

const updates$ = new Subject()

const IdentityContext = React.createContext({
resolve: () =>
Promise.reject(Error('Please set resolve using IdentityProvider')),
})

const IdentityProvider = ({
onResolve,
onShowLocalIdentityModal,
children,
}) => (
<IdentityContext.Provider
value={{
resolve: onResolve,
showLocalIdentityModal: onShowLocalIdentityModal,
updates$,
}}
>
{children}
</IdentityContext.Provider>
)

IdentityProvider.propTypes = {
children: PropTypes.node.isRequired,
onResolve: PropTypes.func.isRequired,
onShowLocalIdentityModal: PropTypes.func.isRequired,
}

const IdentityConsumer = IdentityContext.Consumer

export { IdentityProvider, IdentityConsumer, IdentityContext }
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Badge, IdentityBadge, font } from '@aragon/ui'
import { IdentityContext } from '../IdentityManager/IdentityManager'

function useIdentity(address) {
const [name, setName] = React.useState(null)
const { resolve, updates$, showLocalIdentityModal } = React.useContext(
IdentityContext
)

const handleNameChange = metadata => {
setName(metadata ? metadata.name : null)
}

const handleShowLocalIdentityModal = address => {
// Emit an event whenever the modal is closed (when the promise resolves)
return showLocalIdentityModal(address).then(() => updates$.next(address))
}

React.useEffect(() => {
resolve(address).then(handleNameChange)

const subscription = updates$.subscribe(updatedAddress => {
if (updatedAddress.toLowerCase() === address.toLowerCase()) {
// Resolve and update state when the identity have been updated
resolve(address).then(handleNameChange)
}
})
return () => subscription.unsubscribe()
}, [address])

return [name, handleShowLocalIdentityModal]
}

const LocalIdentityBadge = ({ entity, ...props }) => {
const [label, showLocalIdentityModal] = useIdentity(entity)
const handleClick = () => showLocalIdentityModal(entity)
return (
<IdentityBadge
{...props}
customLabel={label || ''}
entity={entity}
popoverAction={{
label: `${label ? 'Edit' : 'Add'} custom label`,
onClick: handleClick,
}}
popoverTitle={
label ? (
<Wrap>
<Label>{label}</Label>
<StyledBadge>Custom label</StyledBadge>
</Wrap>
) : (
'Address'
)
}
/>
)
}

LocalIdentityBadge.propTypes = {
entity: PropTypes.string.isRequired,
}

const Wrap = styled.div`
display: grid;
align-items: center;
grid-template-columns: auto 1fr;
padding-right: 24px;
`

const Label = styled.span`
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`

const StyledBadge = styled(Badge)`
margin-left: 16px;
text-transform: uppercase;
${font({ size: 'xxsmall' })};
`

export default LocalIdentityBadge
4 changes: 2 additions & 2 deletions apps/voting/app/src/components/VotePanelContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
import styled from 'styled-components'
import {
Button,
IdentityBadge,
Info,
SafeLink,
SidePanelSplit,
Expand All @@ -13,6 +12,7 @@ import {
blockExplorerUrl,
theme,
} from '@aragon/ui'
import LocalIdentityBadge from './LocalIdentityBadge/LocalIdentityBadge.js'
import provideNetwork from '../utils/provideNetwork'
import { VOTE_NAY, VOTE_YEA } from '../vote-types'
import { round } from '../math-utils'
Expand Down Expand Up @@ -227,7 +227,7 @@ class VotePanelContent extends React.Component {
<Label>Created By</Label>
</h2>
<Creator>
<IdentityBadge entity={creator} networkType={network.type} />
<LocalIdentityBadge entity={creator} networkType={network.type} />
</Creator>
</Part>
<SidePanelSeparator />
Expand Down

0 comments on commit 59a024f

Please sign in to comment.