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>
115 lines
4.3 KiB
TypeScript
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>
|
|
);
|
|
}
|