import { useState, type FormEvent } from "react"; import { motion } from "motion/react"; import emailjs from "@emailjs/browser"; import { useTranslation } from "../i18n"; import { useRateLimit } from "../hooks"; import { config } from "../config"; import { Button, Input, Textarea } from "../components/ui"; import { sanitizeInput, isValidEmail } from "../utils/security"; import styles from "./Contact.module.css"; const NAME_MAX_LENGTH = 100; const EMAIL_MAX_LENGTH = 254; const SUBJECT_MAX_LENGTH = 200; const MESSAGE_MAX_LENGTH = 5000; interface FormData { name: string; email: string; subject: string; message: string; } interface FormErrors { name?: string; email?: string; subject?: string; message?: string; } export function Contact() { const { t } = useTranslation(); const [formData, setFormData] = useState({ name: "", email: "", subject: "", message: "", }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [submitStatus, setSubmitStatus] = useState< "idle" | "success" | "error" >("idle"); const [rateLimitError, setRateLimitError] = useState(false); const { checkRateLimit } = useRateLimit("contact-form", 60000); // 1 minute cooldown const validateForm = (): boolean => { const newErrors: FormErrors = {}; if (!formData.name.trim()) { newErrors.name = "Required"; } else if (formData.name.length > NAME_MAX_LENGTH) { newErrors.name = `Max ${NAME_MAX_LENGTH} characters`; } if (!formData.email.trim()) { newErrors.email = "Required"; } else if (formData.email.length > EMAIL_MAX_LENGTH) { newErrors.email = `Max ${EMAIL_MAX_LENGTH} characters`; } else if (!isValidEmail(formData.email)) { newErrors.email = "Invalid email"; } if (!formData.subject.trim()) { newErrors.subject = "Required"; } else if (formData.subject.length > SUBJECT_MAX_LENGTH) { newErrors.subject = `Max ${SUBJECT_MAX_LENGTH} characters`; } if (!formData.message.trim()) { newErrors.message = "Required"; } else if (formData.message.length > MESSAGE_MAX_LENGTH) { newErrors.message = `Max ${MESSAGE_MAX_LENGTH} characters`; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setRateLimitError(false); if (!validateForm()) return; if (!checkRateLimit()) { setRateLimitError(true); return; } setIsSubmitting(true); setSubmitStatus("idle"); try { const templateParams = { name: sanitizeInput(formData.name), email: sanitizeInput(formData.email), title: sanitizeInput(formData.subject), message: sanitizeInput(formData.message), reply_to: formData.email, }; // Send to Admin await emailjs.send( config.emailJs.serviceId, config.emailJs.templateIdAdmin, templateParams, { publicKey: config.emailJs.publicKey }, ); // Send Auto-reply to User await emailjs.send( config.emailJs.serviceId, config.emailJs.templateIdUser, templateParams, { publicKey: config.emailJs.publicKey }, ); setSubmitStatus("success"); setFormData({ name: "", email: "", subject: "", message: "" }); } catch (error) { console.error("EmailJS Error:", error); setSubmitStatus("error"); } finally { setIsSubmitting(false); } }; const handleChange = (field: keyof FormData, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { setErrors((prev) => ({ ...prev, [field]: undefined })); } }; return (
{/* Hero */}

{t.contact.title}

{t.contact.subtitle}

{/* Content */}
{/* Form */}

{t.contact.intro}

handleChange("name", e.target.value)} error={errors.name} maxLength={NAME_MAX_LENGTH} /> handleChange("email", e.target.value)} error={errors.email} maxLength={EMAIL_MAX_LENGTH} /> handleChange("subject", e.target.value)} error={errors.subject} maxLength={SUBJECT_MAX_LENGTH} />