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>
This commit is contained in:
1 parent
974a5ace4c
commit
c0ba3a3798
50 files changed
+2773
-1
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
WORKSHOP_CHALLENGE_IDS,
|
||||
WORKSHOP_HONEYPOT_FIELD,
|
||||
WORKSHOP_MEMBER_COUNTS,
|
||||
WORKSHOP_ORGANIZATION_KINDS,
|
||||
WORKSHOP_ROLES,
|
||||
WORKSHOP_STARTED_AT_FIELD,
|
||||
type WorkshopFieldErrors,
|
||||
type WorkshopFormValues,
|
||||
} from "../../workshopSignup";
|
||||
|
||||
const CHALLENGE_SET = new Set<string>(WORKSHOP_CHALLENGE_IDS);
|
||||
const KIND_SET = new Set<string>(WORKSHOP_ORGANIZATION_KINDS);
|
||||
const MEMBER_SET = new Set<string>(WORKSHOP_MEMBER_COUNTS);
|
||||
const ROLE_SET = new Set<string>(WORKSHOP_ROLES);
|
||||
|
||||
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
export interface ParsedWorkshopSignup {
|
||||
values: WorkshopFormValues;
|
||||
fieldErrors: WorkshopFieldErrors;
|
||||
honeypot: string;
|
||||
startedAt: number | null;
|
||||
}
|
||||
|
||||
function asString(value: unknown): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
function asStringList(value: unknown): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter((item): item is string => typeof item === "string");
|
||||
}
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return [value];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function parseWorkshopSignupPayload(raw: unknown): ParsedWorkshopSignup {
|
||||
const record =
|
||||
raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
|
||||
|
||||
const challenges = [
|
||||
...new Set(asStringList(record.challenges).map((item) => item.trim())),
|
||||
].filter((item) => CHALLENGE_SET.has(item));
|
||||
|
||||
const values: WorkshopFormValues = {
|
||||
challenges,
|
||||
challengeOther: asString(record.challengeOther).trim().slice(0, 200),
|
||||
unspokenQuestion: asString(record.unspokenQuestion).trim(),
|
||||
organizationName: asString(record.organizationName).trim(),
|
||||
organizationKind: asString(record.organizationKind).trim(),
|
||||
memberCount: asString(record.memberCount).trim(),
|
||||
contactName: asString(record.contactName).trim(),
|
||||
email: asString(record.email).trim().toLowerCase(),
|
||||
role: asString(record.role).trim(),
|
||||
sourcePage: asString(record.sourcePage).trim() || "/workshops",
|
||||
sourceUrl: asString(record.sourceUrl).trim().slice(0, 2000),
|
||||
};
|
||||
|
||||
const fieldErrors: WorkshopFieldErrors = {};
|
||||
|
||||
if (values.challenges.length === 0) fieldErrors.challenges = "required";
|
||||
if (
|
||||
values.challenges.includes("something-else") &&
|
||||
values.challengeOther.length === 0
|
||||
) {
|
||||
fieldErrors.challengeOther = "required";
|
||||
}
|
||||
if (values.unspokenQuestion.length > 1000) fieldErrors.unspokenQuestion = "tooLong";
|
||||
if (values.organizationName.length === 0) fieldErrors.organizationName = "required";
|
||||
else if (values.organizationName.length > 150) fieldErrors.organizationName = "tooLong";
|
||||
if (!KIND_SET.has(values.organizationKind)) fieldErrors.organizationKind = "required";
|
||||
if (!MEMBER_SET.has(values.memberCount)) fieldErrors.memberCount = "required";
|
||||
if (values.contactName.length === 0) fieldErrors.contactName = "required";
|
||||
else if (values.contactName.length > 100) fieldErrors.contactName = "tooLong";
|
||||
if (values.email.length === 0 || !EMAIL_PATTERN.test(values.email) || values.email.length > 254) {
|
||||
fieldErrors.email = values.email.length === 0 ? "required" : "invalid";
|
||||
}
|
||||
if (values.role.length > 0 && !ROLE_SET.has(values.role)) fieldErrors.role = "invalid";
|
||||
|
||||
const startedRaw = record[WORKSHOP_STARTED_AT_FIELD];
|
||||
const startedAt =
|
||||
typeof startedRaw === "number"
|
||||
? startedRaw
|
||||
: typeof startedRaw === "string" && startedRaw.trim().length > 0
|
||||
? Number(startedRaw)
|
||||
: null;
|
||||
|
||||
return {
|
||||
values,
|
||||
fieldErrors,
|
||||
honeypot: asString(record[WORKSHOP_HONEYPOT_FIELD]).trim(),
|
||||
startedAt: startedAt !== null && Number.isFinite(startedAt) ? startedAt : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function workshopSignupFromFormData(formData: FormData): ParsedWorkshopSignup {
|
||||
return parseWorkshopSignupPayload({
|
||||
challenges: formData.getAll("challenges"),
|
||||
challengeOther: formData.get("challengeOther"),
|
||||
unspokenQuestion: formData.get("unspokenQuestion"),
|
||||
organizationName: formData.get("organizationName"),
|
||||
organizationKind: formData.get("organizationKind"),
|
||||
memberCount: formData.get("memberCount"),
|
||||
contactName: formData.get("contactName"),
|
||||
email: formData.get("email"),
|
||||
role: formData.get("role"),
|
||||
sourcePage: formData.get("sourcePage"),
|
||||
sourceUrl: formData.get("sourceUrl"),
|
||||
[WORKSHOP_STARTED_AT_FIELD]: formData.get(WORKSHOP_STARTED_AT_FIELD),
|
||||
[WORKSHOP_HONEYPOT_FIELD]: formData.get(WORKSHOP_HONEYPOT_FIELD),
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user