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>
91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { apiRoute } from "../../../lib/server/apiRoute";
|
|
import { submitWorkshopSignup } from "../../../lib/server/workshopSignup";
|
|
import { attachWorkshopSignupDraft } from "../../../lib/server/workshopSignupDraft";
|
|
import { readLimitedJson } from "../../../lib/server/validation/requestBody";
|
|
import { workshopSignupFromFormData } from "../../../lib/server/validation/workshopSignupSchemas";
|
|
|
|
const SCOPE = "workshop-signup.submit";
|
|
|
|
function wantsHtmlForm(request: NextRequest): boolean {
|
|
const type = request.headers.get("content-type") ?? "";
|
|
return (
|
|
type.includes("application/x-www-form-urlencoded") ||
|
|
type.includes("multipart/form-data")
|
|
);
|
|
}
|
|
|
|
function redirectBack(
|
|
request: NextRequest,
|
|
status: string,
|
|
largeOrganization: boolean,
|
|
): NextResponse {
|
|
const url = new URL("/workshops", request.url);
|
|
url.searchParams.set("signup", status);
|
|
if (status === "sent") {
|
|
url.searchParams.set("band", largeOrganization ? "large" : "standard");
|
|
}
|
|
url.hash = "workshop-signup";
|
|
return NextResponse.redirect(url, 303);
|
|
}
|
|
|
|
export const POST = apiRoute(SCOPE, async (request: NextRequest, _ctx, { requestId }) => {
|
|
const htmlForm = wantsHtmlForm(request);
|
|
|
|
let raw: unknown;
|
|
if (htmlForm) {
|
|
const formData = await request.formData();
|
|
const parsed = workshopSignupFromFormData(formData);
|
|
raw = {
|
|
...parsed.values,
|
|
company_website: parsed.honeypot,
|
|
startedAt: parsed.startedAt,
|
|
};
|
|
} else {
|
|
const parsedBody = await readLimitedJson(request);
|
|
if (parsedBody.ok === false) return parsedBody.response;
|
|
raw = parsedBody.value;
|
|
}
|
|
|
|
const result = await submitWorkshopSignup(raw, requestId);
|
|
|
|
if (htmlForm) {
|
|
const status =
|
|
result.status === "unavailable" ? "failed" : result.status === "sent" ? "sent" : result.status;
|
|
const response = redirectBack(request, status === "rate_limited" ? "failed" : status, result.largeOrganization);
|
|
if (result.status !== "sent") {
|
|
attachWorkshopSignupDraft(response, {
|
|
values: result.values,
|
|
fieldErrors: result.fieldErrors,
|
|
});
|
|
}
|
|
return response;
|
|
}
|
|
|
|
if (result.status === "invalid") {
|
|
return NextResponse.json(
|
|
{
|
|
ok: false,
|
|
status: "invalid",
|
|
fieldErrors: result.fieldErrors,
|
|
values: result.values,
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (result.status === "rate_limited") {
|
|
return NextResponse.json({ ok: false, status: "rate_limited" }, { status: 429 });
|
|
}
|
|
|
|
if (result.status === "failed" || result.status === "unavailable") {
|
|
return NextResponse.json({ ok: false, status: "failed" }, { status: 502 });
|
|
}
|
|
|
|
return NextResponse.json({
|
|
ok: true,
|
|
status: "sent",
|
|
largeOrganization: result.largeOrganization,
|
|
});
|
|
});
|