Skip to content

Commit

Permalink
Progress on governance card component
Browse files Browse the repository at this point in the history
Fix for proposal search
  • Loading branch information
jmrossy committed Feb 17, 2024
1 parent ff40550 commit c178bc3
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 34 deletions.
53 changes: 22 additions & 31 deletions src/app/governance/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Section } from 'src/components/layout/Section';
import { DropdownModal } from 'src/components/menus/Dropdown';
import { H1 } from 'src/components/text/headers';
import { links } from 'src/config/links';
import { ProposalCard } from 'src/features/governance/ProposalCard';
import { ProposalStage } from 'src/features/governance/contractTypes';
import {
MergedProposalData,
Expand Down Expand Up @@ -83,22 +84,28 @@ function ProposalList() {
</div>
<div></div>
{filteredProposals ? (
<div className="divide-y divide-taupe-300">
<>
<TabHeaderFilters
activeFilter={filter}
setFilter={setFilter}
counts={headerCounts}
showCount={!isMobile}
className="pb-2 all:space-x-4 md:space-x-6"
className="border-b border-taupe-300 pb-2 all:space-x-4 md:space-x-6"
/>
{filteredProposals.length ? (
filteredProposals.map((data, i) => <ProposalCard key={i} data={data} />)
) : (
<div className="flex justify-center py-10">
<p className="text-center text-taupe-600">No proposals found</p>
</div>
)}
</div>
<div className="space-y-0 divide-y divide-taupe-300">
{filteredProposals.length ? (
filteredProposals.map((data, i) => (
<div key={i} className="py-6 first:pt-0">
<ProposalCard data={data} />
</div>
))
) : (
<div className="flex justify-center py-10">
<p className="text-center text-taupe-600">No proposals found</p>
</div>
)}
</div>
</>
) : (
<div className="flex justify-center py-10">
<SpinnerWithLabel>Loading governance data</SpinnerWithLabel>
Expand All @@ -108,22 +115,6 @@ function ProposalList() {
);
}

function ProposalCard({ data }: { data: MergedProposalData }) {
const { stage, proposal, metadata } = data;

const title =
metadata?.title || metadata?.cgp ? `Proposal CGP-${metadata.cgp}` : `Proposal #${proposal?.id}`;

return (
<div className="flex justify-between">
<div>
<h3 className="font-serif text-lg">{title}</h3>
<div className="text-sm">{stage}</div>
</div>
</div>
);
}

function CtaModal() {
return (
<div className="flex w-fit flex-col space-y-2 border border-taupe-300 bg-taupe-100 bg-diamond-texture bg-right-bottom py-2.5 pl-4 pr-8 md:pr-14">
Expand Down Expand Up @@ -182,11 +173,11 @@ function useFilteredProposals({
.filter(
(p) =>
!query ||
p.proposal?.proposer.includes(query) ||
p.proposal?.url.includes(query) ||
p.metadata?.title.includes(query) ||
p.metadata?.author.includes(query) ||
p.metadata?.url?.includes(query),
p.proposal?.proposer.toLowerCase().includes(query) ||
p.proposal?.url.toLowerCase().includes(query) ||
p.metadata?.title.toLowerCase().includes(query) ||
p.metadata?.author.toLowerCase().includes(query) ||
p.metadata?.url?.toLowerCase().includes(query),
);
}, [proposals, filter, searchQuery]);
}
13 changes: 10 additions & 3 deletions src/config/consts.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
export const DEFAULT_DISPLAY_DECIMALS = 2;
export const DEFAULT_TOKEN_DECIMALS = 18;
export const MIN_REMAINING_BALANCE = 10000000000000000n; // 0.01 CELO
export const AVG_BLOCK_TIMES_MS = 5_000; // 5 seconds
export const EPOCH_DURATION_MS = 86_400_000; // 1 day
export const BALANCE_REFRESH_INTERVAL = 5_000; // 5 seconds

// Locking
export const MIN_REMAINING_BALANCE = 10000000000000000n; // 0.01 CELO

// Staking
export const MIN_GROUP_SCORE_FOR_RANDOM = 90;
export const MIN_INCREMENTAL_VOTE_AMOUNT = 10000000000000000n; // 0.01 CELO

// From the Election contract electableValidators config
export const MAX_NUM_ELECTABLE_VALIDATORS = 110;
export const MAX_NUM_GROUPS_VOTED_FOR = 10;

// Governance
export const QUEUED_STAGE_EXPIRY_TIME = 2_419_200_000; // 4 weeks
export const APPROVAL_STAGE_EXPIRY_TIME = 86_400_000; // 1 day
export const EXECUTION_STAGE_EXPIRY_TIME = 259_200_000; // 3 days
49 changes: 49 additions & 0 deletions src/features/governance/ProposalCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Image from 'next/image';
import { StageBadge } from 'src/features/governance/StageBadge';
import { MergedProposalData } from 'src/features/governance/useGovernanceProposals';
import ClockIcon from 'src/images/icons/clock.svg';
import { trimToLength } from 'src/utils/strings';
import { getHumanReadableTimeString } from 'src/utils/time';

export function ProposalCard({ data }: { data: MergedProposalData }) {
const { stage, proposal, metadata } = data;

const { id, timestamp, expiryTimestamp, votes } = proposal || {};
const { title, timestamp: cgpTimestamp, timestampExecuted, cgp } = metadata || {};

const idValue = id ? `# ${id}` : cgp ? `CGP ${cgp}` : undefined;
const titleValue = title ? trimToLength(title, 50) : undefined;
const proposedTimestamp = timestamp || cgpTimestamp;
const proposedTimeValue = proposedTimestamp
? new Date(proposedTimestamp).toLocaleDateString()
: undefined;
const endTimestamp = timestampExecuted || expiryTimestamp;
const endTimeValue = endTimestamp ? getHumanReadableTimeString(endTimestamp) : undefined;
const endTimeLabel = timestampExecuted ? 'Executed' : 'Expires';

return (
<div className="space-y-2">
<div className="flex items-center space-x-3">
{idValue && (
<div className="rounded-full border border-taupe-300 px-2 py-0.5 text-sm font-light">
{idValue}
</div>
)}
<StageBadge stage={stage} />
{proposedTimeValue && (
<div className="text-sm text-taupe-600">{`Proposed ${proposedTimeValue}`}</div>
)}
</div>
{titleValue && <h3 className="text-xl font-medium">{titleValue}</h3>}
{votes && (
<div className="text-sm text-taupe-600">{`Votes: ${votes.yes} Yes, ${votes.no} No, ${votes.abstain} Abstain`}</div>
)}
{endTimeValue && (
<div className="flex items-center space-x-2">
<Image src={ClockIcon} alt="" width={16} height={16} />
<div className="text-sm font-medium">{`${endTimeLabel} ${endTimeValue}`}</div>
</div>
)}
</div>
);
}
14 changes: 14 additions & 0 deletions src/features/governance/StageBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import clsx from 'clsx';
import { ProposalStage, ProposalStageToStyle } from 'src/features/governance/contractTypes';

export function StageBadge({ stage, className }: { stage: ProposalStage; className?: string }) {
const { color, label } = ProposalStageToStyle[stage];
return (
<div
style={{ backgroundColor: color }}
className={clsx('rounded-full px-2 py-0.5 text-sm font-light', className)}
>
{label}
</div>
);
}
15 changes: 15 additions & 0 deletions src/features/governance/contractTypes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Color } from 'src/styles/Color';

export enum VoteValue {
None = 'none',
Abstain = 'abstain',
Expand All @@ -22,6 +24,18 @@ export enum ProposalStage {
Rejected = 8,
}

export const ProposalStageToStyle: Record<ProposalStage, { color: string; label: string }> = {
[ProposalStage.None]: { color: Color.Sky, label: 'Draft' },
[ProposalStage.Queued]: { color: Color.Lavender, label: 'Upvoting' },
[ProposalStage.Approval]: { color: Color.Lavender, label: 'Approval' },
[ProposalStage.Referendum]: { color: Color.Jade, label: 'Voting' },
[ProposalStage.Execution]: { color: Color.Jade, label: 'Passed' },
[ProposalStage.Expiration]: { color: Color.Red, label: 'Expired' },
[ProposalStage.Executed]: { color: Color.Jade, label: 'Executed' },
[ProposalStage.Withdrawn]: { color: Color.Red, label: 'Withdrawn' },
[ProposalStage.Rejected]: { color: Color.Red, label: 'Rejected' },
};

export const FAILED_PROPOSAL_STAGES = [
ProposalStage.Expiration,
ProposalStage.Rejected,
Expand All @@ -31,6 +45,7 @@ export const FAILED_PROPOSAL_STAGES = [
export interface Proposal {
id: number;
timestamp: number;
expiryTimestamp?: number;
url: string;
proposer: Address;
deposit: bigint;
Expand Down
5 changes: 5 additions & 0 deletions src/features/governance/useGovernanceProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ async function fetchGovernanceProposals(publicClient: PublicClient): Promise<Pro
});
}

// TODO for any proposals in the queued, approval, or execution stages, compute expiryTime
// This will likely require fetching events from celoscan related to the proposal
// to know the exact time of the stage transitions
// QUEUED_STAGE_EXPIRY_TIME, APPROVAL_STAGE_EXPIRY_TIME, EXECUTION_STAGE_EXPIRY_TIME

return proposals;
}

Expand Down
4 changes: 4 additions & 0 deletions src/images/icons/clock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/styles/Color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum Color {

// Accents
Yellow = '#FCFF52',
Red = '#F68098',
Gypsum = '#FCF6F1',
Sand = '#E7E3D4',
Wood = '#655947',
Expand Down

0 comments on commit c178bc3

Please sign in to comment.