feat: public events/polls, friends graph, captcha, SSE hardening, and admin UX

Ship product gaps: register/vote pages, friend add/accept/decline/remove, email verify TTL, captcha on login/forgot, soft-fail user actions, SSE abort/shared client, Commando Centrum error toasts, admin delete for events/polls, and IT/NL i18n fills.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
2026-07-21 21:08:33 +02:00
co-authored by Cursor
parent 2ff08e5127
commit ed7db6e048
76 changed files with 4834 additions and 1376 deletions
+89
View File
@@ -0,0 +1,89 @@
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ContentCard, EmptyState } from "@/components/public/ui";
import { excerpt } from "@/lib/format";
import { formatDate } from "@/lib/format-date";
import { prisma } from "@/lib/prisma";
export const revalidate = 60;
export const metadata = { title: "Polls" };
export default async function PollsPage() {
const t = await getTranslations("pages.polls");
const now = new Date();
const polls = await prisma.websitePoll
.findMany({
where: {
status: { in: ["active", "closed"] },
OR: [{ startsAt: null }, { startsAt: { lte: now } }],
},
orderBy: { createdAt: "desc" },
take: 50,
include: {
_count: { select: { questions: true } },
},
})
.catch(() => []);
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="📊" title={t("title")} subtitle={t("subtitle")} />
<ContentCard padded={polls.length === 0}>
{polls.length === 0 ? (
<EmptyState icon="📊">{t("empty")}</EmptyState>
) : (
<div className="card-grid sm-2 lg-3" style={{ padding: "1rem" }}>
{polls.map((poll) => {
const ended =
poll.status === "closed" ||
(poll.endsAt != null && poll.endsAt.getTime() < Date.now());
return (
<Link
key={poll.id}
href={`/polls/${poll.id}`}
className="card hover"
style={{
color: "inherit",
textDecoration: "none",
display: "grid",
gap: "0.35rem",
}}
>
<p
className="muted"
style={{ margin: 0, fontSize: "0.75rem" }}
>
{ended ? t("statusClosed") : t("statusActive")}
{" · "}
{t("questionsCount", { count: poll._count.questions })}
</p>
<h3 style={{ margin: 0, fontSize: "1.05rem" }}>
{poll.title}
</h3>
{poll.description ? (
<p style={{ margin: 0, fontSize: "0.88rem", lineHeight: 1.5 }}>
{excerpt(poll.description, 120)}
</p>
) : null}
<p
className="muted"
style={{ margin: 0, fontSize: "0.85rem" }}
>
{poll.endsAt
? t("ends", {
date: formatDate(poll.endsAt, "datetime", ""),
})
: formatDate(poll.createdAt, "date", "")}
</p>
</Link>
);
})}
</div>
)}
</ContentCard>
</main>
);
}