Ask organizer modal implemented
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Shared between client (form JSON) and server (Zod + honeypot check).
|
||||
* CR-107 Ask an organizer.
|
||||
*/
|
||||
export const ORGANIZER_INQUIRY_HONEYPOT_FIELD = "company" as const;
|
||||
|
||||
export const ASK_ORGANIZER_INQUIRY_FORM_ID = "ask-organizer-inquiry-form" as const;
|
||||
@@ -56,6 +56,39 @@ export async function sendRuleStakeholderInviteEmail(
|
||||
});
|
||||
}
|
||||
|
||||
/** CR-107: notify support/organizers when a visitor submits the Ask an organizer form. */
|
||||
export async function sendOrganizerInquiryNotification(params: {
|
||||
/** Destination inbox (e.g. from ORGANIZER_INQUIRY_TO). */
|
||||
to: string;
|
||||
fromEmail: string;
|
||||
visitorEmail: string;
|
||||
message: string;
|
||||
requestId: string;
|
||||
}): Promise<void> {
|
||||
const { to, fromEmail, visitorEmail, message, requestId } = params;
|
||||
const url = process.env.SMTP_URL;
|
||||
|
||||
if (!url) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
logger.info(
|
||||
`[dev] Organizer inquiry (request ${requestId}) from ${visitorEmail} to ${to}:\n${message}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error("SMTP_URL is not configured");
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport(url);
|
||||
|
||||
await transporter.sendMail({
|
||||
from: fromEmail,
|
||||
to,
|
||||
replyTo: visitorEmail,
|
||||
subject: `Ask an organizer inquiry from ${visitorEmail}`,
|
||||
text: `Request ID: ${requestId}\nFrom: ${visitorEmail}\n\n${message}\n`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendEmailChangeEmail(
|
||||
to: string,
|
||||
verifyUrl: string,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { z } from "zod";
|
||||
import { ORGANIZER_INQUIRY_HONEYPOT_FIELD } from "../../organizerInquiryConstants";
|
||||
|
||||
const emailSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Email is required")
|
||||
.max(254)
|
||||
.transform((s) => s.toLowerCase())
|
||||
.pipe(z.string().email("Enter a valid email address"));
|
||||
|
||||
const messageSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(10, "Please enter at least 10 characters")
|
||||
.max(10_000, "Message is too long");
|
||||
|
||||
/** Optional honeypot; non-empty after trim indicates a bot. */
|
||||
const honeypotSchema = z
|
||||
.union([z.string(), z.undefined()])
|
||||
.optional()
|
||||
.transform((v) => (typeof v === "string" ? v.trim() : ""));
|
||||
|
||||
export const organizerInquiryBodySchema = z.object({
|
||||
email: emailSchema,
|
||||
message: messageSchema,
|
||||
[ORGANIZER_INQUIRY_HONEYPOT_FIELD]: honeypotSchema,
|
||||
});
|
||||
|
||||
export type OrganizerInquiryBody = z.infer<typeof organizerInquiryBodySchema>;
|
||||
Reference in New Issue
Block a user