Skip to content

Commit

Permalink
Add governance history to delegate page
Browse files Browse the repository at this point in the history
  • Loading branch information
jmrossy committed Mar 30, 2024
1 parent 3fbf805 commit 4dbb43f
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 19 deletions.
48 changes: 44 additions & 4 deletions src/app/delegate/[address]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { FullWidthSpinner } from 'src/components/animation/Spinner';
import { FullWidthSpinner, SpinnerWithLabel } from 'src/components/animation/Spinner';
import { BackLink } from 'src/components/buttons/BackLink';
import { Section } from 'src/components/layout/Section';
import { SocialLogoLink } from 'src/components/logos/SocialLogo';
Expand All @@ -10,9 +10,15 @@ import { SocialLinkType } from 'src/config/types';
import { DelegateButton } from 'src/features/delegation/components/DelegateButton';
import { DelegateeLogo } from 'src/features/delegation/components/DelegateeLogo';
import { DelegatorsTable } from 'src/features/delegation/components/DelegatorsTable';
import { useDelegateeHistory } from 'src/features/delegation/hooks/useDelegateeHistory';
import { useDelegatees } from 'src/features/delegation/hooks/useDelegatees';
import { Delegatee } from 'src/features/delegation/types';
import { ProposalCard } from 'src/features/governance/components/ProposalCard';
import { useGovernanceProposals } from 'src/features/governance/hooks/useGovernanceProposals';
import { VoteTypeToIcon } from 'src/features/governance/types';
import { getLargestVoteType } from 'src/features/governance/utils';
import { usePageInvariant } from 'src/utils/navigation';
import { objLength } from 'src/utils/objects';

export const dynamicParams = true;

Expand Down Expand Up @@ -45,15 +51,15 @@ function DelegateeDescription({ delegatee }: { delegatee: Delegatee }) {
<div className="space-y-4">
<BackLink href="/delegate">Browse delegates</BackLink>
<div className="flex items-center gap-1">
<DelegateeLogo address={delegatee.address} size={86} />
<DelegateeLogo address={delegatee.address} size={90} />
<div className="ml-4 flex flex-col">
<h1 className="font-serif text-2xl md:text-3xl">{delegatee.name}</h1>
<div className="flex items-center space-x-2">
<ShortAddress address={delegatee.address} className="text-sm text-taupe-600" />
<span className="text-sm text-taupe-600"></span>
<span className="text-sm text-taupe-600">{`Since ${dateString}`}</span>
</div>
<div className="mt-1 flex items-center space-x-3">
<div className="mt-1.5 flex items-center space-x-3">
{Object.entries(delegatee.links).map(([type, href], i) => (
<SocialLogoLink key={i} type={type as SocialLinkType} href={href} />
))}
Expand All @@ -66,7 +72,41 @@ function DelegateeDescription({ delegatee }: { delegatee: Delegatee }) {
</div>
</div>
<h2 className="font-serif text-xl">Introduction</h2>
<p style={{ maxWidth: 'min(96vw, 700px)', overflow: 'auto' }}>{delegatee.description}</p>
<p style={{ maxWidth: 'min(96vw, 700px)' }} className="overflow-auto leading-relaxed">
{delegatee.description}
</p>
<GovernanceParticipation delegatee={delegatee} />
</div>
);
}

function GovernanceParticipation({ delegatee }: { delegatee: Delegatee }) {
const { proposalToVotes, isLoading: isLoadingHistory } = useDelegateeHistory(delegatee.address);
const { proposals, isLoading: isLoadingProposals } = useGovernanceProposals();

const isLoading = isLoadingHistory || isLoadingProposals;
const hasVotes = proposalToVotes && objLength(proposalToVotes) > 0;

return (
<div className="flex flex-col space-y-2.5 divide-y border-taupe-300 py-1">
<h2 className="font-serif text-xl">Governance Participation</h2>
{isLoading ? (
<SpinnerWithLabel className="py-10">Loading governance history</SpinnerWithLabel>
) : proposals && hasVotes ? (
Object.entries(proposalToVotes).map(([id, votes], i) => {
const proposal = proposals.find((p) => p.id === parseInt(id));
if (!proposal) return null;
const { type } = getLargestVoteType(votes);
return (
<div key={i} className="pt-2.5">
<ProposalCard propData={proposal} isCompact={true} />
<div className="mt-1.5 text-sm">{`Voted ${type} ${VoteTypeToIcon[type]}`}</div>
</div>
);
})
) : (
<p className="text-gray-600">This delegate has not voted for governance proposals yet</p>
)}
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/app/governance/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ function ProposalList() {
showCount={!isMobile}
className="border-b border-taupe-300 pb-2 pt-1 all:space-x-4 md:space-x-6"
/>
<div className="mt-6 divide-y divide-taupe-300">
<div className="mt-5 divide-y divide-taupe-300">
{filteredProposals.length ? (
filteredProposals.map((data, i) => (
<div key={i} className="py-6 first:pt-0">
<div key={i} className="py-5 first:pt-0">
<ProposalCard propData={data} />
</div>
))
Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useBlockNumber } from 'wagmi';

export function Footer() {
return (
<div className="flex w-full justify-between px-3 py-3 sm:px-5">
<div className="mt-2 flex w-full justify-between px-3 py-3 sm:px-5">
<div className="inline-flex items-start justify-start gap-4">
<FooterIconLink href={links.github} type={SocialLinkType.Github} />
<FooterIconLink href={links.twitter} type={SocialLinkType.Twitter} />
Expand Down
28 changes: 16 additions & 12 deletions src/features/governance/components/ProposalCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import Image from 'next/image';
import Link from 'next/link';
import { A_Blank } from 'src/components/buttons/A_Blank';
Expand All @@ -21,7 +22,15 @@ import { getHumanReadableDuration, getHumanReadableTimeString } from 'src/utils/

const MIN_VOTE_SUM_FOR_GRAPH = 10000000000000000000n; // 10 CELO

export function ProposalCard({ propData }: { propData: MergedProposalData }) {
export function ProposalCard({
propData,
isCompact,
className,
}: {
propData: MergedProposalData;
isCompact?: boolean;
className?: string;
}) {
const { id, proposal, metadata } = propData;

const { expiryTimestamp } = proposal || {};
Expand All @@ -46,10 +55,10 @@ export function ProposalCard({ propData }: { propData: MergedProposalData }) {
}));

return (
<Link href={link} className="space-y-2.5">
<Link href={link} className={clsx('space-y-2.5', className)}>
<ProposalBadgeRow propData={propData} />
{titleValue && <h2 className="text-xl font-medium">{titleValue}</h2>}
{votes && sum > MIN_VOTE_SUM_FOR_GRAPH && (
{titleValue && <h2 className={clsx('font-medium', !isCompact && 'text-lg')}>{titleValue}</h2>}
{!isCompact && votes && sum > MIN_VOTE_SUM_FOR_GRAPH && (
<div className="space-y-2.5">
<StackedBarChart data={barChartData} showBorder={false} height="h-1" />
<div className="flex items-center space-x-5">
Expand All @@ -64,7 +73,7 @@ export function ProposalCard({ propData }: { propData: MergedProposalData }) {
</div>
</div>
)}
{endTimeValue && (
{!isCompact && endTimeValue && (
<div className="flex items-center space-x-2">
<Image src={ClockIcon} alt="" width={16} height={16} />
<div className="text-sm font-medium">{`${endTimeValue}`}</div>
Expand Down Expand Up @@ -140,19 +149,14 @@ function IdBadge({ cgp, id }: { cgp?: number; id?: number }) {
if (!cgp && !id) return null;
const idValue = cgp ? `CGP ${cgp}` : `# ${id}`;
return (
<div className="rounded-full border border-taupe-300 px-2 py-0.5 text-sm font-light">
{idValue}
</div>
<div className="rounded-full border border-taupe-300 px-2 text-sm font-light">{idValue}</div>
);
}

function StageBadge({ stage }: { stage: ProposalStage }) {
const { color, label } = ProposalStageToStyle[stage];
return (
<div
style={{ backgroundColor: color }}
className={'rounded-full px-2 py-0.5 text-sm font-light'}
>
<div style={{ backgroundColor: color }} className={'rounded-full px-2 text-sm font-light'}>
{label}
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions src/features/governance/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export const VoteTypes: VoteType[] = [VoteType.Yes, VoteType.No, VoteType.Abstai
// Used to go from VoteValue enum to Governance Contract's enum
export const OrderedVoteValue = [VoteType.None, VoteType.Abstain, VoteType.No, VoteType.Yes];

export const VoteTypeToIcon = {
[VoteType.None]: '⚪',
[VoteType.Abstain]: '⚪',
[VoteType.No]: '👎',
[VoteType.Yes]: '👍',
};

export type VoteAmounts = {
[VoteType.Yes]: bigint;
[VoteType.No]: bigint;
Expand Down
15 changes: 15 additions & 0 deletions src/features/governance/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { VoteAmounts, VoteType } from 'src/features/governance/types';

export function getLargestVoteType(votes: VoteAmounts) {
let maxType = VoteType.None;
let maxValue = 0n;

for (const [type, value] of Object.entries(votes)) {
if (value && value > maxValue) {
maxType = type as VoteType;
maxValue = value;
}
}

return { type: maxType, value: maxValue };
}

0 comments on commit 4dbb43f

Please sign in to comment.