import { and, asc, eq, inArray } from "drizzle-orm"; import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { auth } from "@/lib/auth"; import { db, WebsitePoll, WebsitePollQuestion, WebsitePollVote, } from "@/lib/db"; import { formatDate } from "@/lib/format-date"; import { PollVoteForm } from "./poll-vote-form"; export default async function PollDetailPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; const pollId = Number.parseInt(id, 10); if (!Number.isFinite(pollId) || pollId <= 0) notFound(); const t = await getTranslations("pages.polls"); const session = await auth(); const userId = Number(session?.user?.id); const [poll] = await db .select() .from(WebsitePoll) .where(eq(WebsitePoll.id, pollId)) .limit(1) .catch(() => []); if (!poll || (poll.status !== "active" && poll.status !== "closed")) { notFound(); } const questions = await db .select() .from(WebsitePollQuestion) .where(eq(WebsitePollQuestion.pollId, poll.id)) .orderBy(asc(WebsitePollQuestion.sortOrder)) .catch(() => []); const now = Date.now(); if ( poll.startsAt && poll.startsAt.getTime() > now && poll.status === "active" ) { // Not started yet โ€” treat as unavailable for guests notFound(); } const hasEnded = poll.status === "closed" || (poll.endsAt != null && poll.endsAt.getTime() < now); const isOpen = poll.status === "active" && !hasEnded; const questionIds = questions.map((q) => q.id); const myVotes = Number.isInteger(userId) && userId > 0 && questionIds.length > 0 ? await db .select() .from(WebsitePollVote) .where( and( eq(WebsitePollVote.userId, userId), inArray(WebsitePollVote.questionId, questionIds), ), ) .catch(() => []) : []; const hasVoted = myVotes.length > 0; const allVotes = poll.showResults === 1 && questionIds.length > 0 ? await db .select({ questionId: WebsitePollVote.questionId, answer: WebsitePollVote.answer, }) .from(WebsitePollVote) .where(inArray(WebsitePollVote.questionId, questionIds)) .catch(() => []) : []; const votesByQuestion = new Map(); for (const vote of allVotes) { const list = votesByQuestion.get(vote.questionId) ?? []; list.push(vote.answer); votesByQuestion.set(vote.questionId, list); } return (

{t("back")}

{poll.description ? (

{poll.description}

) : null}
{isOpen && !hasVoted ? ( session?.user ? ( ({ id: q.id, question: q.question, type: q.type, options: q.options, }))} /> ) : ( {t("loginToVote")} ) ) : ( {hasVoted ? t("alreadyVoted") : hasEnded ? t("pollClosed") : t("cannotVote")} )} {poll.showResults === 1 && (hasVoted || hasEnded) ? (
{questions.map((q) => { const options = q.options .split("\n") .map((o) => o.trim()) .filter(Boolean); const votes = votesByQuestion.get(q.id) ?? []; const total = votes.length; if (q.type === "text" || options.length === 0) { return (

{q.question}

{t("textResponses", { count: total })}

); } const counts = new Map(); for (const opt of options) counts.set(opt, 0); for (const vote of votes) { for (const part of vote .split("\n") .map((a) => a.trim()) .filter(Boolean)) { counts.set(part, (counts.get(part) ?? 0) + 1); } } return (

{q.question}

{options.map((opt) => { const voteCount = counts.get(opt) ?? 0; const pct = total > 0 ? Math.round((voteCount / total) * 100) : 0; return (
{opt} {t("voteCount", { count: voteCount, pct })}
); })}

{t("totalVotes", { count: total })}

); })}
) : null}
); }