auto-claude: subtask-3-1 - Update ContactForm to call API route instead of simulating
Changes: - Replaced simulated API call with real fetch() to /api/contact - Added errorMessage state for custom error messages - Implemented proper response handling for different status codes: * 200: Success message and form reset * 429: Rate limit error with time until retry * 400/500: Display API error messages - Enhanced rate limit error display with human-readable time formatting - Added VERIFICATION_STEPS.md for manual testing guidance Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
# Verification Steps for ContactForm API Integration
|
||||||
|
|
||||||
|
## Manual Verification Required
|
||||||
|
|
||||||
|
The ContactForm has been updated to call the real API route at `/api/contact` instead of simulating the submission.
|
||||||
|
|
||||||
|
### Test in Browser
|
||||||
|
|
||||||
|
1. **Navigate to**: http://localhost:3000/en/contact
|
||||||
|
|
||||||
|
2. **Test Successful Submission**:
|
||||||
|
- Fill in all form fields (name, email, message)
|
||||||
|
- Click "Send Message"
|
||||||
|
- Verify: Success message appears
|
||||||
|
- Verify: Form fields are cleared
|
||||||
|
- Verify: No console errors
|
||||||
|
|
||||||
|
3. **Test Rate Limiting**:
|
||||||
|
- Submit the form 5 times rapidly
|
||||||
|
- On the 6th submission, verify:
|
||||||
|
- Error message appears with rate limit warning
|
||||||
|
- Message shows time until retry (e.g., "Too many requests. Please try again in 60 minutes.")
|
||||||
|
- Form is still functional (not broken)
|
||||||
|
|
||||||
|
4. **Test Validation**:
|
||||||
|
- Try submitting with empty fields
|
||||||
|
- Verify validation errors appear
|
||||||
|
- Try submitting with invalid email
|
||||||
|
- Verify email validation error appears
|
||||||
|
|
||||||
|
5. **Test Error Display**:
|
||||||
|
- Verify error messages are clearly visible
|
||||||
|
- Verify error messages disappear on successful submission
|
||||||
|
- Check that UI remains user-friendly
|
||||||
|
|
||||||
|
### Expected Behavior
|
||||||
|
|
||||||
|
- ✅ Form submits to `/api/contact` with POST request
|
||||||
|
- ✅ Success message displays on 200 response
|
||||||
|
- ✅ Rate limit error displays on 429 response with countdown
|
||||||
|
- ✅ Generic error message displays on other errors
|
||||||
|
- ✅ Form validation works before API call
|
||||||
|
- ✅ Loading state shows during submission
|
||||||
|
- ✅ Form is disabled during submission
|
||||||
|
|
||||||
|
### Changes Made
|
||||||
|
|
||||||
|
1. Added `errorMessage` state to store custom error messages
|
||||||
|
2. Replaced simulated API call with real `fetch()` to `/api/contact`
|
||||||
|
3. Added response status handling:
|
||||||
|
- 200 (OK): Success message, clear form
|
||||||
|
- 429 (Rate Limited): Display time until retry
|
||||||
|
- 400/500: Display API error message
|
||||||
|
4. Improved error message display with custom messages
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ export function ContactForm() {
|
|||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
const [formData, setFormData] = useState<FormData>({
|
const [formData, setFormData] = useState<FormData>({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
@@ -62,14 +63,48 @@ export function ContactForm() {
|
|||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
setSubmitStatus('idle');
|
setSubmitStatus('idle');
|
||||||
|
setErrorMessage('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Simulate API call
|
const response = await fetch('/api/contact', {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
method: 'POST',
|
||||||
setSubmitStatus('success');
|
headers: {
|
||||||
setFormData({ name: '', email: '', message: '' });
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setSubmitStatus('success');
|
||||||
|
setFormData({ name: '', email: '', message: '' });
|
||||||
|
} else if (response.status === 429) {
|
||||||
|
// Rate limit exceeded
|
||||||
|
setSubmitStatus('error');
|
||||||
|
const retryAfter = data.retryAfter || 0;
|
||||||
|
const minutes = Math.ceil(retryAfter / 60);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const remainingMinutes = minutes % 60;
|
||||||
|
|
||||||
|
let timeMessage = '';
|
||||||
|
if (hours > 0) {
|
||||||
|
timeMessage = `${hours} hour${hours > 1 ? 's' : ''}${remainingMinutes > 0 ? ` and ${remainingMinutes} minute${remainingMinutes > 1 ? 's' : ''}` : ''}`;
|
||||||
|
} else {
|
||||||
|
timeMessage = `${minutes} minute${minutes > 1 ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrorMessage(
|
||||||
|
`Too many requests. Please try again in ${timeMessage}.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Other errors (400, 500, etc.)
|
||||||
|
setSubmitStatus('error');
|
||||||
|
setErrorMessage(data.error || t('contactForm.errorMessage'));
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setSubmitStatus('error');
|
setSubmitStatus('error');
|
||||||
|
setErrorMessage(t('contactForm.errorMessage'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@@ -299,7 +334,7 @@ export function ContactForm() {
|
|||||||
className="p-3 bg-red-900/20 border border-red-900/50 rounded-lg"
|
className="p-3 bg-red-900/20 border border-red-900/50 rounded-lg"
|
||||||
>
|
>
|
||||||
<p className="text-red-400 text-sm">
|
<p className="text-red-400 text-sm">
|
||||||
{t('contactForm.errorMessage')}
|
{errorMessage || t('contactForm.errorMessage')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user