Skip to content

Commit

Permalink
Merge pull request #165 from MinaFoundation/feature/voting-fund-distr…
Browse files Browse the repository at this point in the history
…ibution

Feature/voting fund distribution
  • Loading branch information
iluxonchik authored Feb 19, 2025
2 parents 0dc3733 + e93e00c commit 56af338
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 78 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pgt-web-app",
"version": "0.1.68",
"version": "0.1.69",
"private": true,
"type": "module",
"scripts": {
Expand Down
29 changes: 26 additions & 3 deletions src/app/funding-rounds/[id]/summaries/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ const PhaseSummaryDashboard = async ({ params }: Props) => {
defaultDates,
)

// Post-voting phase starts when voting phase ends and continues indefinitely
const postVotingPhaseInfo = fundingRound.votingPhase
? {
startDate: fundingRound.votingPhase.endDate,
endDate: new Date('9999-12-31T23:59:59.999Z'), // Effectively infinite
status:
new Date() > fundingRound.votingPhase.endDate
? ('ongoing' as const)
: ('not-started' as const),
}
: {
startDate: defaultDates.endDate, // If no voting phase, start after default end
endDate: new Date('9999-12-31T23:59:59.999Z'),
status: 'not-started' as const,
}

const phases: PhaseInfo[] = [
{
title: 'Submission Phase',
Expand All @@ -209,12 +225,19 @@ const PhaseSummaryDashboard = async ({ params }: Props) => {
href: `/funding-rounds/${id}/deliberation/summary`,
},
{
title: 'Voting Phase',
description: 'Final voting results and funding distribution',
icon: <CoinsIcon className="h-5 w-5 text-amber-500" />,
title: 'Voting Phase Ranked Results',
description: 'Overview of the ranked voting results',
icon: <VoteIcon className="h-5 w-5 text-emerald-500" />,
...votingPhaseInfo,
href: `/funding-rounds/${id}/voting/summary`,
},
{
title: 'Voting Phase Funds Distribution',
description: 'Funds distribution for the voting phase',
icon: <CoinsIcon className="h-5 w-5 text-amber-500" />,
...postVotingPhaseInfo,
href: `/funding-rounds/${id}/voting-funds/summary`,
},
]

return (
Expand Down
35 changes: 35 additions & 0 deletions src/app/funding-rounds/[id]/voting-funds/summary/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type FC } from 'react'
import { notFound } from 'next/navigation'
import { VotingService } from '@/services/VotingService'
import { prisma } from '@/lib/prisma'
import { VotingPhaseFundsDistributionSummary } from '@/components/phase-summary/VotingPhaseFundsDistributionSummary'

type Props = {
params: Promise<{
id: string
}>
}

const VotingPhaseFundsDistributionSummaryPage = async ({ params }: Props) => {
const { id } = await params
const votingService = new VotingService(prisma)

try {
const summary =
await votingService.getVotingPhaseFundsDistributionSummary(id)
console.log(summary)

return (
<div className="container mx-auto max-w-7xl py-6">
<VotingPhaseFundsDistributionSummary
summary={summary}
fundingRoundId={id}
/>
</div>
)
} catch (error) {
return notFound()
}
}

export default VotingPhaseFundsDistributionSummaryPage
11 changes: 5 additions & 6 deletions src/app/funding-rounds/[id]/voting/summary/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,29 @@ import { type FC } from 'react'
import { notFound } from 'next/navigation'
import { VotingService } from '@/services/VotingService'
import { prisma } from '@/lib/prisma'
import { VotingPhaseSummary } from '@/components/phase-summary/VotingPhaseSummary'
import { VotingPhaseRankedSummary } from '@/components/phase-summary/VotingPhaseRankedSummary'

type Props = {
params: Promise<{
id: string
}>
}

const VotingPhaseSummaryPage = async ({ params }: Props) => {
const VotingPhaseRankedSummaryPage = async ({ params }: Props) => {
const { id } = await params
const votingService = new VotingService(prisma)

try {
const summary = await votingService.getVotingPhaseSummary(id)
console.log(summary)
const summary = await votingService.getVotingPhaseRankedSummary(id)

return (
<div className="container mx-auto max-w-7xl py-6">
<VotingPhaseSummary summary={summary} fundingRoundId={id} />
<VotingPhaseRankedSummary summary={summary} fundingRoundId={id} />
</div>
)
} catch (error) {
return notFound()
}
}

export default VotingPhaseSummaryPage
export default VotingPhaseRankedSummaryPage
134 changes: 85 additions & 49 deletions src/components/phase-summary/ProposalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CoinsIcon,
CalendarIcon,
UsersIcon,
VoteIcon,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { format } from 'date-fns'
Expand All @@ -23,6 +24,7 @@ import {
type CommunityVoteStats,
type SubmissionProposalVote,
type VotingProposalVote,
type RankedVotingProposalVote,
} from '@/types/phase-summary'

type ProposalVote =
Expand All @@ -32,6 +34,7 @@ type ProposalVote =
})
| SubmissionProposalVote
| VotingProposalVote
| RankedVotingProposalVote

interface Props {
proposal: ProposalVote
Expand All @@ -58,55 +61,78 @@ const getEmojiRank = (position: number): string => {
}

const getProposalStatus = (proposal: ProposalVote) => {
const isSubmissionPhase = 'submissionDate' in proposal;
const isVotingPhase = 'isFunded' in proposal;
const isDraft = proposal.status === 'DRAFT';

if (isVotingPhase) {
return (proposal as VotingProposalVote).isFunded ? {
label: 'Funded',
bgColor: 'bg-emerald-50/50 hover:bg-emerald-50/80',
borderColor: 'border-emerald-200/50',
textColor: 'text-emerald-600'
} : {
label: 'Not Funded',
bgColor: 'bg-rose-50/50 hover:bg-rose-50/80',
borderColor: 'border-rose-200/50',
textColor: 'text-rose-600'
};
}

if (isSubmissionPhase) {
return {
label: 'Submitted',
bgColor: 'bg-blue-50/50 hover:bg-blue-50/80',
borderColor: 'border-blue-200/50',
textColor: 'text-blue-600'
};
}

if (isDraft) {
return {
label: 'Draft',
bgColor: 'bg-gray-50/50 hover:bg-gray-50/80',
borderColor: 'border-gray-200/50',
textColor: 'text-gray-600'
};
}

const hasMovedForward = ['DELIBERATION', 'VOTING', 'APPROVED'].includes(proposal.status);
return hasMovedForward ? {
label: 'Approved',
bgColor: 'bg-emerald-50/50 hover:bg-emerald-50/80',
borderColor: 'border-emerald-200/50',
textColor: 'text-emerald-600'
} : {
label: 'Rejected',
bgColor: 'bg-rose-50/50 hover:bg-rose-50/80',
borderColor: 'border-rose-200/50',
textColor: 'text-rose-600'
};
};
const isSubmissionPhase = 'submissionDate' in proposal
const isVotingPhase = 'isFunded' in proposal
const isRankedVoting = 'hasVotes' in proposal
const isDraft = proposal.status === 'DRAFT'

if (isRankedVoting) {
return proposal.status === 'NO_VOTES'
? {
label: 'No OCV Votes',
bgColor: 'bg-gray-50/50 hover:bg-gray-50/80',
borderColor: 'border-gray-200/50',
textColor: 'text-gray-600',
}
: {
label: 'Ranked',
bgColor: 'bg-blue-50/50 hover:bg-blue-50/80',
borderColor: 'border-blue-200/50',
textColor: 'text-blue-600',
}
}

if (isVotingPhase) {
return (proposal as VotingProposalVote).isFunded
? {
label: 'Funded',
bgColor: 'bg-emerald-50/50 hover:bg-emerald-50/80',
borderColor: 'border-emerald-200/50',
textColor: 'text-emerald-600',
}
: {
label: 'Not Funded',
bgColor: 'bg-rose-50/50 hover:bg-rose-50/80',
borderColor: 'border-rose-200/50',
textColor: 'text-rose-600',
}
}

if (isSubmissionPhase) {
return {
label: 'Submitted',
bgColor: 'bg-blue-50/50 hover:bg-blue-50/80',
borderColor: 'border-blue-200/50',
textColor: 'text-blue-600',
}
}

if (isDraft) {
return {
label: 'Draft',
bgColor: 'bg-gray-50/50 hover:bg-gray-50/80',
borderColor: 'border-gray-200/50',
textColor: 'text-gray-600',
}
}

const hasMovedForward = ['DELIBERATION', 'VOTING', 'APPROVED'].includes(
proposal.status,
)
return hasMovedForward
? {
label: 'Approved',
bgColor: 'bg-emerald-50/50 hover:bg-emerald-50/80',
borderColor: 'border-emerald-200/50',
textColor: 'text-emerald-600',
}
: {
label: 'Rejected',
bgColor: 'bg-rose-50/50 hover:bg-rose-50/80',
borderColor: 'border-rose-200/50',
textColor: 'text-rose-600',
}
}

export const ProposalCard: FC<Props> = ({
proposal,
Expand All @@ -116,6 +142,7 @@ export const ProposalCard: FC<Props> = ({
const status = getProposalStatus(proposal)
const isSubmissionPhase = 'submissionDate' in proposal
const isVotingPhase = 'isFunded' in proposal
const isRankedVoting = 'hasVotes' in proposal

return (
<Link href={`/proposals/${proposal.id}`} className="block">
Expand Down Expand Up @@ -207,6 +234,15 @@ export const ProposalCard: FC<Props> = ({
</>
)}
</div>
) : isRankedVoting ? (
<div className="flex items-center gap-1 text-primary">
<VoteIcon className="h-3 w-3" />
<span className="text-xs font-medium">
{(proposal as RankedVotingProposalVote).hasVotes
? 'Ranked in OCV'
: 'No OCV Votes'}
</span>
</div>
) : (
<>
<div className="flex items-center gap-1 text-emerald-600">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type FC } from 'react'
import { CoinsIcon } from 'lucide-react'
import { TooltipProvider } from '@/components/ui/tooltip'
import { type VotingPhaseSummary as VotingPhaseSummaryType } from '@/types/phase-summary'
import { type VotingPhaseFundsDistributionSummary as VotingPhaseFundsDistributionSummaryType } from '@/types/phase-summary'
import {
getPhaseStatus,
getPhaseProgress,
Expand All @@ -13,15 +13,16 @@ import { StatsCard } from './StatsCard'
import { ProposalList } from './ProposalList'
import { BudgetDistributionChart } from '../funding-rounds/BudgetDistributionChart'
import { formatMINA } from '@/lib/format'
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'

interface Props {
summary: VotingPhaseSummaryType
summary: VotingPhaseFundsDistributionSummaryType
fundingRoundId: string
}

export const VotingPhaseSummary: FC<Props> = ({ summary, fundingRoundId }) => {
export const VotingPhaseFundsDistributionSummary: FC<Props> = ({
summary,
fundingRoundId,
}) => {
const phaseStatus = getPhaseStatus(summary.phaseTimeInfo)
const progress = getPhaseProgress(summary.phaseTimeInfo)
const progressColor = getProgressColor(progress)
Expand All @@ -41,8 +42,8 @@ export const VotingPhaseSummary: FC<Props> = ({ summary, fundingRoundId }) => {
return (
<TooltipProvider>
<BasePhaseSummary
title={`${summary.fundingRoundName}'s Voting Phase Summary`}
description="Overview of the voting phase results and funding distribution"
title={`${summary.fundingRoundName}'s Funds Distribution Summary`}
description="Overview of the funding distribution"
phaseStatus={phaseStatus}
stats={
<div className="flex items-center gap-4">
Expand Down
Loading

0 comments on commit 56af338

Please sign in to comment.