import { z } from 'zod' import type { ContractBodyInput } from '@/lib/api/contracts/types' import { defineRouteContract } from '@/lib/api/contracts/types' import { NO_EMAIL_HEADER_CONTROL_CHARS_REGEX } from '@/lib/messaging/email/utils' import { quickValidateEmail } from '@/lib/messaging/email/validation' export const CONTACT_TOPIC_VALUES = [ 'general', 'support', 'integration', 'feature_request', 'sales', 'partnership', 'billing', 'other', ] as const export const CONTACT_TOPIC_OPTIONS = [ { value: 'general', label: 'General question' }, { value: 'support', label: 'Technical support' }, { value: 'integration', label: 'Integration request' }, { value: 'feature_request', label: 'Feature request' }, { value: 'sales', label: 'Sales & pricing' }, { value: 'partnership', label: 'Partnership' }, { value: 'billing', label: 'Billing' }, { value: 'other', label: 'Other' }, ] as const export const contactRequestSchema = z.object({ name: z .string() .trim() .min(1, 'Name is required') .max(120, 'Name must be 120 characters or less') .regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'), email: z .string() .trim() .min(1, 'Email is required') .max(320) .transform((value) => value.toLowerCase()) .refine((value) => quickValidateEmail(value).isValid, 'Enter a valid email'), company: z .string() .trim() .max(120, 'Company must be 120 characters or less') .optional() .transform((value) => (value && value.length > 0 ? value : undefined)), topic: z.enum(CONTACT_TOPIC_VALUES, { error: 'Please select a topic', }), subject: z .string() .trim() .min(1, 'Subject is required') .max(200, 'Subject must be 200 characters or less') .regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'), message: z .string() .trim() .min(1, 'Message is required') .max(5000, 'Message must be 5,000 characters or less'), }) export type ContactRequestPayload = z.infer export type ContactRequestBody = z.input export const submitContactBodySchema = contactRequestSchema.extend({ website: z.string().optional(), captchaToken: z.string().optional(), }) export function getContactTopicLabel(value: ContactRequestPayload['topic']): string { return CONTACT_TOPIC_OPTIONS.find((option) => option.value === value)?.label ?? value } export type HelpEmailType = 'bug' | 'feedback' | 'feature_request' | 'other' /** * Map a contact topic to the confirmation-email type. Only `feature_request` has * a matching email label ("Feature Request"); every other topic — support, sales, * billing, etc. — resolves to `other` ("General Inquiry") so the confirmation copy * never mislabels the request (e.g. calling a support inquiry a "bug report"). */ export function mapContactTopicToHelpType(topic: ContactRequestPayload['topic']): HelpEmailType { return topic === 'feature_request' ? 'feature_request' : 'other' } export const contactResponseSchema = z.object({ success: z.literal(true), message: z.string(), }) export type SubmitContactResult = z.output export const submitContactContract = defineRouteContract({ method: 'POST', path: '/api/contact', body: submitContactBodySchema, response: { mode: 'json', schema: contactResponseSchema, }, }) export type SubmitContactBody = ContractBodyInput