feat: add client-side rate limiting to contact form

- Added `useRateLimit` hook
- Integrated hook into `Contact.tsx`
- Added translations for rate limit error
- Added unit tests
- Fixed type error in `Button.tsx` to allow build to pass
This commit is contained in:
google-labs-jules[bot]
2026-01-26 01:49:05 +00:00
parent 13df58342a
commit 839e1bf82f
11 changed files with 190 additions and 2 deletions

View File

@@ -2,6 +2,7 @@ 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 } from "../utils/security";
@@ -38,6 +39,8 @@ export function Contact() {
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 = {};
@@ -73,8 +76,14 @@ export function Contact() {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setRateLimitError(false);
if (!validateForm()) return;
if (!checkRateLimit()) {
setRateLimitError(true);
return;
}
setIsSubmitting(true);
setSubmitStatus("idle");
@@ -215,6 +224,16 @@ export function Contact() {
{t.contact.form.error}
</motion.p>
)}
{rateLimitError && (
<motion.p
className={styles.error}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
>
{t.contact.form.rateLimit}
</motion.p>
)}
</form>
</motion.div>