224 lines
7.7 KiB
TypeScript
224 lines
7.7 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { useTranslations } from 'next-intl';
|
|
import { Button, Heading, Card, Input, Textarea, Label } from '@/components/ui';
|
|
import { sendContactFormAction } from '@/app/actions/contact';
|
|
import { useAnalytics } from '@/components/analytics/useAnalytics';
|
|
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
|
|
|
|
export default function ContactForm() {
|
|
const t = useTranslations('Contact');
|
|
const { trackEvent } = useAnalytics();
|
|
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
|
|
const [hasStarted, setHasStarted] = useState(false);
|
|
|
|
const handleFocus = (fieldId: string) => {
|
|
// Initial form start
|
|
if (!hasStarted) {
|
|
setHasStarted(true);
|
|
trackEvent(AnalyticsEvents.FORM_START, {
|
|
form_id: 'contact_form',
|
|
form_name: 'Contact',
|
|
});
|
|
}
|
|
|
|
// Field-level transparency
|
|
trackEvent(AnalyticsEvents.FORM_FIELD_FOCUS, {
|
|
form_id: 'contact_form',
|
|
field_id: fieldId,
|
|
});
|
|
};
|
|
|
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
e.preventDefault();
|
|
setStatus('submitting');
|
|
|
|
const formData = new FormData(e.currentTarget);
|
|
const email = formData.get('email') as string;
|
|
|
|
try {
|
|
const result = await sendContactFormAction(formData);
|
|
if (result?.success) {
|
|
trackEvent('contact_form_submission', {
|
|
form_type: 'general',
|
|
email,
|
|
});
|
|
setStatus('success');
|
|
(e.target as HTMLFormElement).reset();
|
|
} else {
|
|
console.error('Contact form submission failed:', { email, error: result.error });
|
|
trackEvent(AnalyticsEvents.FORM_ERROR, {
|
|
form_id: 'contact_form',
|
|
error: result.error || 'submission_failed',
|
|
});
|
|
setStatus('error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Contact form submission error:', { email, error });
|
|
trackEvent(AnalyticsEvents.FORM_ERROR, {
|
|
form_id: 'contact_form',
|
|
error: (error as Error).message || 'unexpected_error',
|
|
});
|
|
setStatus('error');
|
|
}
|
|
}
|
|
|
|
if (status === 'success') {
|
|
return (
|
|
<Card
|
|
className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-none shadow-2xl text-center"
|
|
role="alert"
|
|
aria-live="polite"
|
|
>
|
|
<div className="w-20 h-20 bg-accent rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg shadow-accent/20">
|
|
<svg
|
|
className="w-10 h-10 text-primary-dark"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<Heading level={3} className="mb-4">
|
|
{t('form.successTitle') || 'Message Sent!'}
|
|
</Heading>
|
|
<p className="text-text-secondary text-lg mb-8">
|
|
{t('form.successDesc') ||
|
|
'Thank you for your message. We will get back to you as soon as possible.'}
|
|
</p>
|
|
<Button onClick={() => setStatus('idle')} variant="saturated">
|
|
{t('form.sendAnother') || 'Send another message'}
|
|
</Button>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (status === 'error') {
|
|
return (
|
|
<Card
|
|
className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-destructive/20 shadow-2xl text-center bg-destructive/5 animate-slide-up"
|
|
role="alert"
|
|
aria-live="assertive"
|
|
>
|
|
<div className="w-20 h-20 bg-destructive rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg shadow-destructive/20">
|
|
<svg
|
|
className="w-10 h-10 text-destructive-foreground"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
>
|
|
<circle cx="12" cy="12" r="10" />
|
|
<line x1="15" y1="9" x2="9" y2="15" strokeLinecap="round" strokeLinejoin="round" />
|
|
<line x1="9" y1="9" x2="15" y2="15" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</div>
|
|
<Heading level={3} className="mb-4 text-destructive font-black">
|
|
{t('form.errorTitle') || 'Submission Failed!'}
|
|
</Heading>
|
|
<p className="text-destructive/80 text-lg mb-8 leading-relaxed font-medium">
|
|
{t('form.error') || 'Something went wrong. Please check your input and try again.'}
|
|
</p>
|
|
<Button
|
|
onClick={() => setStatus('idle')}
|
|
variant="destructive"
|
|
size="lg"
|
|
className="w-full"
|
|
>
|
|
{t('form.tryAgain') || 'Try Again'}
|
|
</Button>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-none shadow-2xl animate-slide-up">
|
|
<Heading level={3} subtitle={t('form.subtitle')} className="mb-6 md:mb-10">
|
|
{t('form.title')}
|
|
</Heading>
|
|
<form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8">
|
|
<div className="space-y-1 md:space-y-2">
|
|
<Label htmlFor="contact-name">{t('form.name')}</Label>
|
|
<Input
|
|
type="text"
|
|
id="contact-name"
|
|
name="name"
|
|
autoComplete="name"
|
|
enterKeyHint="next"
|
|
onFocus={() => handleFocus('contact-name')}
|
|
aria-label={t('form.name')}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="space-y-1 md:space-y-2">
|
|
<Label htmlFor="contact-email">{t('form.email')}</Label>
|
|
<Input
|
|
type="email"
|
|
id="contact-email"
|
|
name="email"
|
|
autoComplete="email"
|
|
inputMode="email"
|
|
enterKeyHint="next"
|
|
placeholder={t('form.emailPlaceholder')}
|
|
onFocus={() => handleFocus('contact-email')}
|
|
aria-label={t('form.email')}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="md:col-span-2 space-y-1 md:space-y-2">
|
|
<Label htmlFor="contact-message">{t('form.message')}</Label>
|
|
<Textarea
|
|
id="contact-message"
|
|
name="message"
|
|
rows={4}
|
|
enterKeyHint="send"
|
|
placeholder={t('form.messagePlaceholder')}
|
|
onFocus={() => handleFocus('contact-message')}
|
|
aria-label={t('form.message')}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="md:col-span-2 pt-2 md:pt-4">
|
|
<Button
|
|
type="submit"
|
|
variant="saturated"
|
|
size="lg"
|
|
disabled={status === 'submitting'}
|
|
className="w-full shadow-xl shadow-saturated/20 md:h-16 md:px-10 md:text-xl active:scale-[0.98] transition-transform"
|
|
>
|
|
{status === 'submitting' ? (
|
|
<span className="flex items-center gap-2">
|
|
<svg
|
|
className="animate-spin h-5 w-5 text-white"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
></circle>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
></path>
|
|
</svg>
|
|
{t('form.submitting') || 'Sending...'}
|
|
</span>
|
|
) : (
|
|
t('form.submit')
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
);
|
|
}
|