From 0f59df9b3532058c7b9f335018b06ec2d25be5cc Mon Sep 17 00:00:00 2001 From: Damjan Savic Date: Sun, 25 Jan 2026 12:10:15 +0100 Subject: [PATCH] auto-claude: subtask-3-2 - Add rate limit feedback to ContactForm UI - Added rate limit translations for en, de, and sr locales - Added state to track remaining attempts from X-RateLimit-Remaining header - Display warning when remaining attempts are low (<=2) - Show user-friendly error messages with time until reset for 429 errors - Added AlertTriangle icon for visual feedback on warnings - All messages now use i18n translation keys for multilingual support --- .auto-claude-status | 10 +++++----- src/components/contact/ContactForm.tsx | 26 ++++++++++++++++++++++++-- src/messages/de.json | 13 ++++++++++++- src/messages/en.json | 13 ++++++++++++- src/messages/sr.json | 13 ++++++++++++- 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 272c89b..c2d956d 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,23 +3,23 @@ "spec": "017-replace-in-memory-rate-limiter-with-persistent-sol", "state": "building", "subtasks": { - "completed": 5, + "completed": 7, "total": 12, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Add New Persistent Rate Limiter", + "current": "Migrate Contact Form", "id": null, - "total": 4 + "total": 2 }, "workers": { "active": 0, "max": 1 }, "session": { - "number": 3, + "number": 5, "started_at": "2026-01-25T11:52:31.718294" }, - "last_update": "2026-01-25T11:56:14.044309" + "last_update": "2026-01-25T12:07:33.420389" } \ No newline at end of file diff --git a/src/components/contact/ContactForm.tsx b/src/components/contact/ContactForm.tsx index cd87b6c..d15be49 100644 --- a/src/components/contact/ContactForm.tsx +++ b/src/components/contact/ContactForm.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { useTranslations } from 'next-intl'; import { motion } from 'framer-motion'; -import { MapPin, Phone, Mail, Send, Loader2, CheckCircle2 } from 'lucide-react'; +import { MapPin, Phone, Mail, Send, Loader2, CheckCircle2, AlertTriangle } from 'lucide-react'; import Image from 'next/image'; interface FormData { @@ -24,6 +24,7 @@ export function ContactForm() { message: '' }); const [errors, setErrors] = useState>({}); + const [remainingAttempts, setRemainingAttempts] = useState(null); const validateForm = () => { const newErrors: Partial = {}; @@ -76,12 +77,19 @@ export function ContactForm() { const data = await response.json(); + // Parse rate limit headers + const rateLimitRemaining = response.headers.get('X-RateLimit-Remaining'); + if (rateLimitRemaining !== null) { + setRemainingAttempts(parseInt(rateLimitRemaining, 10)); + } + if (response.ok) { setSubmitStatus('success'); setFormData({ name: '', email: '', message: '' }); } else if (response.status === 429) { // Rate limit exceeded setSubmitStatus('error'); + setRemainingAttempts(0); const retryAfter = data.retryAfter || 0; const minutes = Math.ceil(retryAfter / 60); const hours = Math.floor(minutes / 60); @@ -95,7 +103,7 @@ export function ContactForm() { } setErrorMessage( - `Too many requests. Please try again in ${timeMessage}.` + t('contactForm.rateLimit.error', { time: timeMessage }) ); } else { // Other errors (400, 500, etc.) @@ -314,6 +322,20 @@ export function ContactForm() { + {/* Rate limit warning - show when attempts are low */} + {remainingAttempts !== null && remainingAttempts > 0 && remainingAttempts <= 2 && ( + + +

+ {t('contactForm.rateLimit.warning', { count: remainingAttempts })} +

+
+ )} + {submitStatus === 'success' && (