-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(APP-3347): Implement ProposalVoting module component (#245)
- Loading branch information
Showing
51 changed files
with
2,005 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './clipboardUtils'; | ||
export * from './formatterUtils'; | ||
export * from './invariant'; | ||
export * from './mergeRefs'; | ||
export * from './responsiveUtils'; | ||
export * from './ssrUtils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { invariant, invariantError } from './invariant'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { invariant, invariantError } from './invariant'; | ||
|
||
describe('invariant utils', () => { | ||
it('does not throw error on condition success', () => { | ||
expect(() => invariant(5 < 10, 'error')).not.toThrow(); | ||
}); | ||
|
||
it('throws invariant error on condition error', () => { | ||
const error = 'oops'; | ||
const expectedError = new Error(error); | ||
expectedError.name = invariantError; | ||
expect(() => invariant(5 > 10, error)).toThrow(new Error(error)); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const invariantError = 'Invariant'; | ||
|
||
export function invariant(condition: boolean, message: string): asserts condition { | ||
if (!condition) { | ||
const error = new Error(message); | ||
error.name = invariantError; | ||
|
||
throw error; | ||
} | ||
|
||
return; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './proposalActions'; | ||
export * from './proposalDataListItem'; | ||
export * from './proposalVoting'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { ProposalVotingBreakdownMultisig } from './proposalVotingBreakdownMultisig'; | ||
import { ProposalVotingBreakdownToken } from './proposalVotingBreakdownToken'; | ||
import { ProposalVotingContainer } from './proposalVotingContainer'; | ||
import { ProposalVotingDetails } from './proposalVotingDetails'; | ||
import { ProposalVotingStage } from './proposalVotingStage'; | ||
import { ProposalVotingVotes } from './proposalVotingVotes'; | ||
|
||
export const ProposalVoting = { | ||
BreakdownMultisig: ProposalVotingBreakdownMultisig, | ||
BreakdownToken: ProposalVotingBreakdownToken, | ||
Container: ProposalVotingContainer, | ||
Details: ProposalVotingDetails, | ||
Stage: ProposalVotingStage, | ||
Votes: ProposalVotingVotes, | ||
}; | ||
|
||
export * from './proposalVotingBreakdownMultisig'; | ||
export * from './proposalVotingBreakdownToken'; | ||
export * from './proposalVotingContainer'; | ||
export * from './proposalVotingDefinitions'; | ||
export * from './proposalVotingDetails'; | ||
export * from './proposalVotingStage'; | ||
export * from './proposalVotingVotes'; |
226 changes: 226 additions & 0 deletions
226
src/modules/components/proposal/proposalVoting/proposalVoting.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { DateTime } from 'luxon'; | ||
import { useState } from 'react'; | ||
import { Button, DataList } from '../../../../core'; | ||
import { type IVoteDataListItemStructureProps, VoteDataListItem } from '../../vote'; | ||
import { ProposalVoting, ProposalVotingStatus } from '../index'; | ||
|
||
const meta: Meta<typeof ProposalVoting.Container> = { | ||
title: 'Modules/Components/Proposal/ProposalVoting/ProposalVoting', | ||
component: ProposalVoting.Container, | ||
parameters: { | ||
design: { | ||
type: 'figma', | ||
url: 'https://www.figma.com/design/ISSDryshtEpB7SUSdNqAcw/Aragon-ODS?node-id=16752-20193&m=dev', | ||
}, | ||
}, | ||
}; | ||
|
||
type Story = StoryObj<typeof ProposalVoting.Container>; | ||
|
||
const getTotalVotes = (votes: IVoteDataListItemStructureProps[], indicator: string) => | ||
votes.reduce( | ||
(accumulator, { voteIndicator, votingPower }) => | ||
accumulator + (voteIndicator === indicator ? Number(votingPower) : 0), | ||
0, | ||
); | ||
|
||
const filterVotes = (votes: IVoteDataListItemStructureProps[], search?: string) => | ||
votes.filter( | ||
(vote) => | ||
search == null || | ||
search.length === 0 || | ||
vote.voter.address.includes(search) || | ||
vote.voter.name?.includes(search), | ||
); | ||
|
||
const tokenVotes: IVoteDataListItemStructureProps[] = [ | ||
{ | ||
voter: { address: '0x17366cae2b9c6C3055e9e3C78936a69006BE5409', name: 'cgero.eth' }, | ||
isDelegate: true, | ||
voteIndicator: 'yes', | ||
votingPower: 47289374, | ||
tokenSymbol: 'ARA', | ||
}, | ||
{ | ||
voter: { address: '0xd5fb864ACfD6BB2f72939f122e89fF7F475924f5', name: 'sio.eth' }, | ||
voteIndicator: 'yes', | ||
votingPower: 1238948, | ||
tokenSymbol: 'ARA', | ||
}, | ||
{ | ||
voter: { address: '0xF6ad40D5D477ade0C640eaD49944bdD0AA1fBF05' }, | ||
voteIndicator: 'no', | ||
votingPower: 8495, | ||
tokenSymbol: 'ARA', | ||
}, | ||
{ | ||
voter: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', name: 'vitalik.eth' }, | ||
voteIndicator: 'abstain', | ||
votingPower: 69420000, | ||
tokenSymbol: 'ARA', | ||
}, | ||
{ | ||
voter: { address: '0xe11BFCBDd43745d4Aa6f4f18E24aD24f4623af04', name: 'cdixon.eth' }, | ||
voteIndicator: 'yes', | ||
votingPower: 66749851, | ||
tokenSymbol: 'ARA', | ||
}, | ||
]; | ||
|
||
const multisigVotes: IVoteDataListItemStructureProps[] = [ | ||
{ | ||
voter: { address: '0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7', name: 'ens.eth' }, | ||
voteIndicator: 'approve', | ||
}, | ||
{ | ||
voter: { address: '0x650235a0889CAe912673AAD13Ff75d1F1A175487' }, | ||
voteIndicator: 'approve', | ||
}, | ||
{ | ||
voter: { address: '0xDCFfFFA68464A4AFC96EEf885844631A439cE625' }, | ||
voteIndicator: 'approve', | ||
}, | ||
{ | ||
voter: { address: '0x2536c09E5F5691498805884fa37811Be3b2BDdb4' }, | ||
voteIndicator: 'approve', | ||
}, | ||
]; | ||
|
||
const tokenSettings = [ | ||
{ term: 'Strategy', definition: '1 Token → 1 Vote' }, | ||
{ term: 'Voting options', definition: 'Yes, Abstain, or No' }, | ||
{ term: 'Minimum support', definition: '>50%' }, | ||
{ term: 'Minimum participation (Quorum)', definition: '≥62.42K of 1M DEGEN (≥6.942)' }, | ||
{ term: 'Early execution', definition: 'Yes' }, | ||
{ term: 'Vote replacement', definition: 'No' }, | ||
{ term: 'Minimum duration', definition: '7 days' }, | ||
]; | ||
|
||
const getMultisigSettings = (minApprovals: number) => [ | ||
{ term: 'Strategy', definition: '1 Address → 1 Vote' }, | ||
{ term: 'Voting options', definition: 'Approve' }, | ||
{ term: 'Minimum approval', definition: `${minApprovals} of 5` }, | ||
]; | ||
|
||
/** | ||
* Usage example of the ProposalVoting module component for multi-stage proposals | ||
*/ | ||
export const MultiStage: Story = { | ||
args: { | ||
title: 'Voting', | ||
description: | ||
'The proposal must pass all governance stages to be accepted and potential onchain actions to execute.', | ||
activeStage: '0', | ||
className: 'max-w-[560px]', | ||
}, | ||
render: (args) => { | ||
const [tokenSearch, setTokenSearch] = useState<string | undefined>(''); | ||
const [multisigSearch, setMultisigSearch] = useState<string | undefined>(''); | ||
|
||
const filteredTokenVotes = filterVotes(tokenVotes, tokenSearch); | ||
const minApprovals = 4; | ||
|
||
return ( | ||
<ProposalVoting.Container {...args}> | ||
<ProposalVoting.Stage | ||
name="Token holder voting" | ||
status={ProposalVotingStatus.ACTIVE} | ||
startDate={DateTime.now().toMillis()} | ||
endDate={DateTime.now().plus({ days: 5 }).toMillis()} | ||
> | ||
<ProposalVoting.BreakdownToken | ||
tokenSymbol="ARA" | ||
totalYes={getTotalVotes(tokenVotes, 'yes')} | ||
totalNo={getTotalVotes(tokenVotes, 'no')} | ||
totalAbstain={getTotalVotes(tokenVotes, 'abstain')} | ||
supportThreshold={50} | ||
minParticipation={15} | ||
tokenTotalSupply={9451231259} | ||
> | ||
<Button variant="primary" size="md" className="md:self-start"> | ||
Vote on proposal | ||
</Button> | ||
</ProposalVoting.BreakdownToken> | ||
<ProposalVoting.Votes> | ||
<DataList.Root itemsCount={filteredTokenVotes.length} entityLabel="Votes"> | ||
<DataList.Filter searchValue={tokenSearch} onSearchValueChange={setTokenSearch} /> | ||
<DataList.Container> | ||
{filteredTokenVotes.map((vote) => ( | ||
<VoteDataListItem.Structure key={vote.voter.address} {...vote} /> | ||
))} | ||
</DataList.Container> | ||
<DataList.Pagination /> | ||
</DataList.Root> | ||
</ProposalVoting.Votes> | ||
<ProposalVoting.Details settings={tokenSettings} /> | ||
</ProposalVoting.Stage> | ||
<ProposalVoting.Stage | ||
name="Founders approval" | ||
status={ProposalVotingStatus.PENDING} | ||
startDate={DateTime.now().plus({ days: 7 }).toMillis()} | ||
endDate={DateTime.now().plus({ days: 10 }).toMillis()} | ||
> | ||
<ProposalVoting.BreakdownMultisig approvalsAmount={0} minApprovals={minApprovals} /> | ||
<ProposalVoting.Votes> | ||
<DataList.Root itemsCount={0} entityLabel="Votes"> | ||
<DataList.Filter searchValue={multisigSearch} onSearchValueChange={setMultisigSearch} /> | ||
<DataList.Container | ||
emptyState={{ heading: 'No votes', description: 'Stage has no votes' }} | ||
/> | ||
<DataList.Pagination /> | ||
</DataList.Root> | ||
</ProposalVoting.Votes> | ||
<ProposalVoting.Details settings={getMultisigSettings(minApprovals)} /> | ||
</ProposalVoting.Stage> | ||
</ProposalVoting.Container> | ||
); | ||
}, | ||
}; | ||
|
||
/** | ||
* Usage example of the ProposalVoting module component for single-stage proposals | ||
*/ | ||
export const SingleStage: Story = { | ||
args: { | ||
title: 'Voting', | ||
description: 'The proposal must pass the voting to be accepted and potential onchain actions to execute.', | ||
className: 'max-w-[560px]', | ||
}, | ||
render: (args) => { | ||
const [search, setSearch] = useState<string | undefined>(''); | ||
|
||
const filteredVotes = filterVotes(multisigVotes, search); | ||
const minApprovals = 5; | ||
|
||
return ( | ||
<ProposalVoting.Container {...args}> | ||
<ProposalVoting.Stage | ||
name="Token holder voting" | ||
status={ProposalVotingStatus.ACTIVE} | ||
startDate={DateTime.now().toMillis()} | ||
endDate={DateTime.now().plus({ hours: 7 }).toMillis()} | ||
> | ||
<ProposalVoting.BreakdownMultisig | ||
approvalsAmount={multisigVotes.length} | ||
minApprovals={minApprovals} | ||
/> | ||
<ProposalVoting.Votes> | ||
<DataList.Root itemsCount={filteredVotes.length} entityLabel="Votes"> | ||
<DataList.Filter searchValue={search} onSearchValueChange={setSearch} /> | ||
<DataList.Container> | ||
{filteredVotes.map((vote) => ( | ||
<VoteDataListItem.Structure key={vote.voter.address} {...vote} /> | ||
))} | ||
</DataList.Container> | ||
<DataList.Pagination /> | ||
</DataList.Root> | ||
</ProposalVoting.Votes> | ||
<ProposalVoting.Details settings={getMultisigSettings(minApprovals)} /> | ||
</ProposalVoting.Stage> | ||
</ProposalVoting.Container> | ||
); | ||
}, | ||
}; | ||
|
||
export default meta; |
4 changes: 4 additions & 0 deletions
4
src/modules/components/proposal/proposalVoting/proposalVotingBreakdownMultisig/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { | ||
ProposalVotingBreakdownMultisig, | ||
type IProposalVotingBreakdownMultisigProps, | ||
} from './proposalVotingBreakdownMultisig'; |
Oops, something went wrong.