Files
community-rule/app/(admin)/workshop-inquiries/page.tsx
T
adilalloandCursor c0ba3a3798 Add the Workshops page and keep each sign-up on the site.
The form is built once to the settled field set, and a valid send is saved before anyone is emailed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-03 19:56:49 -07:00

115 lines
4.3 KiB
TypeScript

import type { Metadata } from "next";
import { notFound, redirect } from "next/navigation";
import messages from "../../../messages/en/index";
import { routeMetadata } from "../../../lib/siteMetadata";
import { isDatabaseConfigured } from "../../../lib/server/env";
import { prisma } from "../../../lib/server/db";
import {
grantWorkshopInquiryAdmin,
isWorkshopInquiryAdmin,
workshopInquiryAdminToken,
} from "../../../lib/server/workshopInquiryAdmin";
export const metadata: Metadata = routeMetadata("/workshop-inquiries", {
title: messages.metadata.workshopInquiries.title,
});
async function unlock(formData: FormData) {
"use server";
const password = String(formData.get("password") ?? "");
const ok = await grantWorkshopInquiryAdmin(password);
redirect(ok ? "/workshop-inquiries" : "/workshop-inquiries?denied=1");
}
export default async function WorkshopInquiriesPage({
searchParams,
}: {
searchParams: Promise<{ denied?: string }>;
}) {
if (!workshopInquiryAdminToken()) notFound();
const copy = messages.pages.workshopInquiries;
const params = await searchParams;
const allowed = await isWorkshopInquiryAdmin();
if (!allowed) {
return (
<section className="mx-auto flex w-full max-w-md flex-col gap-4 px-6 py-16">
<h1 className="x-large-heading text-[var(--color-content-default-primary)]">{copy.title}</h1>
<form action={unlock} className="flex flex-col gap-3">
<label className="medium-label" htmlFor="workshop-inquiry-token">
{copy.unlockLabel}
</label>
<input
id="workshop-inquiry-token"
name="password"
type="password"
autoComplete="current-password"
className="h-12 rounded-[12px] border border-[var(--color-border-default-tertiary)] bg-black px-4"
/>
{params.denied ? (
<p className="small-paragraph text-[var(--color-content-default-utility-negative)]">
{copy.unlockError}
</p>
) : null}
<button
type="submit"
className="h-12 rounded-full bg-white px-4 font-medium text-black"
>
{copy.unlockButton}
</button>
</form>
</section>
);
}
const rows = isDatabaseConfigured()
? await prisma.workshopInquiry.findMany({ orderBy: { createdAt: "desc" }, take: 200 })
: [];
return (
<section className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-6 py-12">
<div className="flex items-center justify-between gap-4">
<h1 className="x-large-heading text-[var(--color-content-default-primary)]">{copy.title}</h1>
<a href="/api/workshop-inquiries/export" className="medium-label underline">
{copy.export}
</a>
</div>
{rows.length === 0 ? (
<p className="medium-paragraph">{copy.empty}</p>
) : (
<div className="overflow-x-auto">
<table className="w-full border-collapse text-left text-[14px]">
<thead>
<tr className="border-b border-[var(--color-border-default-secondary)]">
<th className="py-2 pr-4">{copy.columns.when}</th>
<th className="py-2 pr-4">{copy.columns.organization}</th>
<th className="py-2 pr-4">{copy.columns.kind}</th>
<th className="py-2 pr-4">{copy.columns.members}</th>
<th className="py-2 pr-4">{copy.columns.route}</th>
<th className="py-2 pr-4">{copy.columns.contact}</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row.id} className="border-b border-[var(--color-border-default-secondary)] align-top">
<td className="py-3 pr-4 whitespace-nowrap">{row.createdAt.toISOString().slice(0, 10)}</td>
<td className="py-3 pr-4">{row.organizationName}</td>
<td className="py-3 pr-4">{row.organizationKind}</td>
<td className="py-3 pr-4">{row.memberCount}</td>
<td className="py-3 pr-4">{row.routeSuggestion}</td>
<td className="py-3 pr-4">
{row.contactName}
<br />
{row.email}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</section>
);
}