Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: governance proposals dashboard #5357

Merged
merged 10 commits into from Dec 8, 2022
@@ -1,5 +1,18 @@
<script lang="typescript">
import { onMount } from 'svelte'
import { Pane, Proposals, ProposalsDetails, VotingPower } from '@ui'
import { getVotingEvents } from '@core/profile-manager'
import type { IProposal } from '@core/governance/interfaces'
import { createProposalsFromEvents } from '@core/governance/utils'

let proposals: IProposal[]
let loaded = false

onMount(async () => {
const events = await getVotingEvents()
proposals = createProposalsFromEvents(events)
loaded = true
})
</script>

<div class="w-full h-full flex flex-nowrap p-8 relative flex-1 space-x-6 bg-gray-50 dark:bg-gray-900">
Expand All @@ -13,6 +26,8 @@
</div>
<span class="block w-0.5 h-full bg-gray-200" />
<div class="w-2/3">
<Proposals />
{#if loaded}
<Proposals {proposals} />
{/if}
</div>
</div>
3 changes: 2 additions & 1 deletion packages/shared/components/ProposalStatusInfo.svelte
@@ -1,6 +1,7 @@
<script lang="typescript">
import { ProposalStatusTimelineTooltip, ProposalStatusPill } from 'shared/components'
import { ProposalStatus, Position } from 'shared/components/enums'
import { Position } from 'shared/components/enums'
import { ProposalStatus } from '@core/governance/enums'

export let milestones: Record<ProposalStatus, number>
export let status: ProposalStatus
Expand Down
Expand Up @@ -2,7 +2,8 @@
import { formatDate, localize } from '@core/i18n'
import { networkStatus } from '@core/network'
import { milestoneToDate } from '@core/utils'
import { Position, ProposalStatus, Text, TextType, Tooltip } from 'shared/components'
import { Position, Text, TextType, Tooltip } from 'shared/components'
import { ProposalStatus } from '@core/governance/enums'

export let milestones: Record<ProposalStatus, number>
export let status: ProposalStatus
Expand Down
157 changes: 5 additions & 152 deletions packages/shared/components/Proposals.svelte
@@ -1,120 +1,11 @@
<script lang="typescript">
import { ProposalStatusInfo, Text, TooltipIcon } from 'shared/components'
import { Text, ProposalCard } from 'shared/components'
import { localize } from '@core/i18n'
import { FontWeight, Position, ProposalStatus } from './enums'
import { Icon as IconEnum } from '@auxiliary/icon'
import { GovernanceRoute, governanceRouter } from '@core/router'
import { selectedProposal } from '@core/governance'
import { FontWeight } from './enums'

// used data to display in UI
type Proposal = {
dao?: DAO
title: string
status: ProposalStatus
hasVoted?: boolean
milestones?: Record<ProposalStatus, number>
}
import { IProposal } from '@core/governance/interfaces'

// future DAO type
type DAO = {
name: string
icon: IconEnum
}

// mocked DAO
const IotaDAO: DAO = {
name: 'IOTA Foundation',
icon: IconEnum.Iota,
}

// mocked proposals
const proposals: Proposal[] = [
{
title: 'Important Soonaverse decision',
status: ProposalStatus.Announcement,
},
{
dao: IotaDAO,
title: 'The New Shimmer Governance',
status: ProposalStatus.VotingOpen,
hasVoted: true,
},
{
dao: IotaDAO,
title: 'Short proposal',
status: ProposalStatus.Counting,
hasVoted: true,
},
{
title: 'Medium long proposal',
status: ProposalStatus.Counting,
hasVoted: true,
},
{
title: 'Important proposal',
status: ProposalStatus.Counting,
},
{
title: 'Future DAO proposal',
status: ProposalStatus.Closed,
hasVoted: true,
},
{
title: 'DAO decisions',
status: ProposalStatus.Closed,
},
{
title: 'Super extra double very long proposal',
status: ProposalStatus.Closed,
},
{
title: 'Super extra double very long proposal',
status: ProposalStatus.Closed,
},
{
dao: IotaDAO,
title: 'DAO decisions',
status: ProposalStatus.Closed,
},
]

// just used to mock data
function getMilestonesFromStatus(status: ProposalStatus): Record<ProposalStatus, number> {
const milestones: Record<ProposalStatus, number> = {
[ProposalStatus.Announcement]: 3359093,
[ProposalStatus.VotingOpen]: 3359093,
[ProposalStatus.Counting]: 3359093,
[ProposalStatus.Closed]: 3359093,
}

switch (status) {
case ProposalStatus.Announcement:
milestones[ProposalStatus.Announcement] = 2359093
break
case ProposalStatus.VotingOpen:
milestones[ProposalStatus.Announcement] = 2359093
milestones[ProposalStatus.VotingOpen] = 2359093
break
case ProposalStatus.Counting:
milestones[ProposalStatus.Announcement] = 2359093
milestones[ProposalStatus.VotingOpen] = 2359093
milestones[ProposalStatus.Counting] = 2359093
break
case ProposalStatus.Closed:
milestones[ProposalStatus.Announcement] = 2359093
milestones[ProposalStatus.VotingOpen] = 2359093
milestones[ProposalStatus.Counting] = 2359093
milestones[ProposalStatus.Closed] = 2359093
break
}

return milestones
}

function handleProposalClick(proposal: Proposal): void {
$selectedProposal = { ...proposal, milestones: getMilestonesFromStatus(proposal.status) }
$governanceRouter.goTo(GovernanceRoute.Details)
}
export let proposals: IProposal[] = []
</script>

<proposals-container class="flex flex-col h-full">
Expand All @@ -125,45 +16,7 @@
</header-container>
<ul class="grid grid-cols-2 gap-6 flex-1 overflow-y-scroll">
{#each proposals as proposal}
<proposal-box
on:click={() => handleProposalClick(proposal)}
class="flex flex-col p-6 border border-solid border-gray-200 rounded-xl cursor-pointer
{proposal.status === ProposalStatus.Closed ? 'bg-transparent' : 'bg-white'}"
>
<div class="flex items-center gap-1.5 mb-5">
{#if proposal.dao}
<TooltipIcon
icon={proposal.dao.icon}
size="small"
classes="p-0.5 rounded-full bg-black text-white"
iconClasses="text-white"
>
<Text smaller overrideColor fontWeight={FontWeight.semibold} classes="text-gray-600"
>{proposal.dao.name}</Text
>
</TooltipIcon>
{/if}
<Text fontWeight={FontWeight.semibold} fontSize="14" classes="truncate">{proposal.title}</Text>
</div>
<div class="flex justify-between items-center">
<ProposalStatusInfo
status={proposal.status}
milestones={getMilestonesFromStatus(proposal.status)}
/>
{#if proposal.hasVoted}
<TooltipIcon
icon={IconEnum.Voted}
size="small"
position={Position.Left}
iconClasses="text-gray-500"
>
<Text smaller overrideColor fontWeight={FontWeight.semibold} classes="text-gray-600">
{localize('views.governance.proposals.voted')}
</Text>
</TooltipIcon>
{/if}
</div>
</proposal-box>
<ProposalCard {proposal} />
{/each}
</ul>
</proposals-container>
@@ -1,7 +1,7 @@
<script lang="typescript">
import { Pill } from 'shared/components'
import { ProposalStatus } from 'shared/components/enums'
import { localize } from '@core/i18n'
import { ProposalStatus } from '@core/governance/enums'

export let status: ProposalStatus

Expand Down
1 change: 0 additions & 1 deletion packages/shared/components/enums/index.ts
Expand Up @@ -4,6 +4,5 @@ export * from './button-size.enum'
export * from './font-weight.enum'
export * from './nft-media-size.enum'
export * from './position.enum'
export * from './proposal-status.enum'
export * from './tab.enum'
export * from './text-type.enum'
50 changes: 50 additions & 0 deletions packages/shared/components/organisms/ProposalCard.svelte
@@ -0,0 +1,50 @@
<script lang="typescript">
import { ProposalStatusInfo, Text, TooltipIcon } from 'shared/components'
import { IProposal } from '@core/governance/interfaces'
import { selectedProposal } from '@core/governance/stores'
import { ProposalStatus } from '@core/governance/enums'
import { GovernanceRoute, governanceRouter } from '@core/router'

import { FontWeight, Position } from '../enums'
import { Icon } from '@auxiliary/icon/enums'
import { localize } from '@core/i18n'

export let proposal: IProposal

function handleProposalClick(): void {
$selectedProposal = proposal
$governanceRouter.goTo(GovernanceRoute.Details)
}
</script>

<proposal-card
on:click={handleProposalClick}
class="flex flex-col p-6 border border-solid border-gray-200 rounded-xl cursor-pointer h-32
{proposal.status === ProposalStatus.Closed ? 'bg-transparent' : 'bg-white'}"
>
<div class="flex items-center gap-1.5 mb-5">
{#if proposal.organization}
<TooltipIcon
icon={proposal.organization.icon}
size="small"
classes="p-0.5 rounded-full bg-black text-white"
iconClasses="text-white"
>
<Text smaller overrideColor fontWeight={FontWeight.semibold} classes="text-gray-600"
>{proposal.organization.name}</Text
>
</TooltipIcon>
{/if}
<Text fontWeight={FontWeight.semibold} fontSize="14" classes="truncate">{proposal.title}</Text>
</div>
<div class="flex justify-between items-center">
<ProposalStatusInfo status={proposal.status} milestones={proposal.milestones} />
{#if proposal.hasVoted}
<TooltipIcon icon={Icon.Voted} size="small" position={Position.Left} iconClasses="text-gray-500">
<Text smaller overrideColor fontWeight={FontWeight.semibold} classes="text-gray-600">
{localize('views.governance.proposals.voted')}
</Text>
</TooltipIcon>
{/if}
</div>
</proposal-card>
1 change: 1 addition & 0 deletions packages/shared/components/organisms/index.js
Expand Up @@ -2,3 +2,4 @@ export { default as AccountAssetsList } from './AccountAssetsList.svelte'
export { default as AccountSummary } from './AccountSummary.svelte'
export { default as NodeConfigurationForm } from './NodeConfigurationForm.svelte'
export { default as NodeListTable } from './NodeListTable.svelte'
export { default as ProposalCard } from './ProposalCard.svelte'
Expand Up @@ -26,6 +26,7 @@
showAppNotification({
type: 'success',
message: localize('views.governance.proposals.successRegister'),
alert: true,
})
closePopup()
} catch (err) {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/lib/core/governance/enums/index.ts
@@ -0,0 +1 @@
export * from './proposal-status.enum'
2 changes: 2 additions & 0 deletions packages/shared/lib/core/governance/interfaces/index.ts
@@ -0,0 +1,2 @@
export * from './organization.interface'
export * from './proposal.interface'
@@ -0,0 +1,6 @@
import { Icon } from '@auxiliary/icon'

export interface IOrganization {
name: string
icon: Icon
}
@@ -0,0 +1,10 @@
import { ProposalStatus } from '../enums'
import { IOrganization } from './organization.interface'

export interface IProposal {
organization?: IOrganization
title: string
status: ProposalStatus
hasVoted?: boolean
milestones?: Record<ProposalStatus, number>
}
@@ -1,18 +1,4 @@
import { writable } from 'svelte/store'
import { ProposalStatus } from '../../../../components/enums'
import { Icon as IconEnum } from '@auxiliary/icon'
import { IProposal } from '@core/governance/interfaces'

type DAO = {
name: string
icon: IconEnum
}

type Proposal = {
dao?: DAO
title: string
status: ProposalStatus
hasVoted?: boolean
milestones?: Record<ProposalStatus, number>
}

export const selectedProposal = writable<Proposal>(null)
export const selectedProposal = writable<IProposal>(null)
@@ -0,0 +1,41 @@
import { get } from 'svelte/store'

import { Event } from '@iota/wallet'

import { ProposalStatus } from '@core/governance/enums'
import { IProposal } from '@core/governance/interfaces'
import { nodeInfo } from '@core/network'

export function createProposalsFromEvents(events: Event[]): IProposal[] {
const proposals: IProposal[] = events.map(({ data }) => {
const proposal = {
title: data.name,
status: ProposalStatus.Announcement,
milestones: {
[ProposalStatus.Announcement]: 0, // TODO: fix this
[ProposalStatus.VotingOpen]: data.milestoneIndexCommence,
[ProposalStatus.Counting]: data.milestoneIndexStart,
[ProposalStatus.Closed]: data.milestoneIndexEnd,
},
}

const status = getLatestStatus(proposal)
if (status) {
proposal.status = status
}
return proposal
})

return proposals
}

function getLatestStatus(proposal: IProposal): ProposalStatus {
const latestMilestoneIndex = get(nodeInfo)?.status?.latestMilestone.index
const milestoneDifferences = Object.entries(proposal?.milestones).map(([status, milestone]) => ({
status,
milestoneDifference: latestMilestoneIndex - milestone,
}))
const passedMilestones = milestoneDifferences.filter(({ milestoneDifference }) => milestoneDifference > 0)
const lastPassedMilestone = passedMilestones.pop()
return <ProposalStatus>lastPassedMilestone?.status
}
1 change: 1 addition & 0 deletions packages/shared/lib/core/governance/utils/index.ts
@@ -0,0 +1 @@
export * from './createProposalsFromEvents'
@@ -0,0 +1,8 @@
import { get } from 'svelte/store'
import { Event } from '@iota/wallet'
import { profileManager } from '@core/profile-manager'

export function getVotingEvents(): Promise<Event[]> {
const manager = get(profileManager)
return manager.getParticipationEvents()
}