import Link from 'next/link' import { redirect, notFound } from 'next/navigation' import { createClient } from '@/lib/supabase/server' import type { Proposal, ProposalVote } from '@/lib/types' import { ProposalStatusBadge } from '../proposal-badges' import { ProposalActions } from './proposal-actions' import { MarkdownBody } from '@/components/markdown' export const dynamic = 'force-dynamic' export default async function ProposalDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) redirect('/login') const { data: member } = await supabase .from('space_members') .select('id, space_id, role, display_name') .eq('user_id', user.id) .in('status', ['current', 'unverified', 'late']) .maybeSingle() if (!member?.space_id) redirect('/signup') const { data: proposalRaw } = await supabase .from('proposals') .select('*') .eq('id', id) .eq('space_id', member.space_id) .maybeSingle() if (!proposalRaw) notFound() const proposal = proposalRaw as unknown as Proposal const { data: votesRaw } = await supabase .from('proposal_votes') .select('*') .eq('proposal_id', proposal.id) .order('voted_at', { ascending: false }) const votes = (votesRaw ?? []) as unknown as ProposalVote[] // Resolve voter display names from space_members. const voterIds = Array.from(new Set(votes.map(v => v.member_id))) const { data: voters } = voterIds.length ? await supabase .from('space_members') .select('id, display_name') .in('id', voterIds) : { data: [] as Array<{ id: string; display_name: string | null }> } const voterNames = new Map((voters ?? []).map(m => [m.id, m.display_name])) const myVote = votes.find(v => v.member_id === (member as { id: string }).id) ?? null const isAdminOrBoard = member.role === 'admin' || member.role === 'board' const votingOpen = proposal.status === 'open' && proposal.voting_opens_at !== null && proposal.voting_closes_at !== null && new Date(proposal.voting_opens_at) <= new Date() && new Date(proposal.voting_closes_at) > new Date() const totalCounted = proposal.outcome_yes + proposal.outcome_no + proposal.outcome_abstain const quorumPct = proposal.quorum_required > 0 ? Math.min(100, Math.round((totalCounted / proposal.quorum_required) * 100)) : 0 return (
{proposal.proposal_type.replace(/_/g, ' ')} {proposal.proposer_name ? ` · proposed by ${proposal.proposer_name}` : ''} {' · '} {proposal.threshold.replace(/_/g, ' ')} required
{proposal.body &&Tally
Outcome: {proposal.passed ? 'PASSED' : 'DID NOT PASS'} {proposal.decided_at ? ` · ${new Date(proposal.decided_at).toLocaleString()}` : ''}
)}Votes ({votes.length})
{votes.length === 0 ? (No votes cast yet.
) : ({voterNames.get(v.member_id) ?? 'Unknown member'}
{v.comment &&{v.comment}
} {v.recusal_reason && (Recused: {v.recusal_reason}
)}{label}
{value}