ci-electrical/web/components/ContactForm.tsx

267 lines
9.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect } from 'react';
import { track } from '@/lib/analytics';
import Image from 'next/image';
export default function ContactForm({
compact = false,
variant = 'light'
}: {
compact?: boolean;
variant?: 'light' | 'dark';
}) {
const [ok, setOk] = useState(false);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const labelColor = variant === 'dark' ? 'text-white' : 'text-gray-700';
const submit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setErrors({});
const formData = new FormData(e.currentTarget);
const data: Record<string, string> = {};
for (const [key, value] of formData) {
data[key] = value.toString();
}
// Basic validation
const newErrors: Record<string, string> = {};
if (!data.name) newErrors.name = 'Name is required';
if (!data.phone) newErrors.phone = 'Phone is required';
if (!data.email) newErrors.email = 'Email is required';
if (!data.serviceType) newErrors.serviceType = 'Service type is required';
if (!data.issue) newErrors.issue = 'Brief issue description is required';
if (!data.preferredTime) newErrors.preferredTime = 'Preferred time is required';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
setLoading(false);
return;
}
try {
// TODO: wire to API or form service
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
track('form_submit', { source: 'quote_form', compact });
setOk(true);
} catch (error) {
console.error('Form submission failed:', error);
} finally {
setLoading(false);
}
};
if (!mounted) {
// Render a stable skeleton on server and on initial client render to avoid hydration mismatches
return (
<div className={`bg-white rounded-lg shadow-lg p-6 ${compact ? 'max-w-sm' : 'max-w-lg'} mx-auto`}>
<div className="h-6 w-32 bg-gray-200 rounded mb-6 mx-auto" />
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="h-10 bg-gray-200 rounded" />
<div className="h-10 bg-gray-200 rounded" />
</div>
<div className="h-10 bg-gray-200 rounded" />
<div className="h-10 bg-gray-200 rounded" />
<div className="h-24 bg-gray-200 rounded" />
<div className="h-10 bg-gray-200 rounded" />
<div className="h-12 bg-gray-300 rounded" />
</div>
</div>
);
}
if (ok) {
return (
<div className="bg-green-50 border border-green-200 rounded-lg p-6 text-center">
<div className="text-4xl mb-4"></div>
<h3 className="font-bold text-xl text-green-600 mb-2">Thank You!</h3>
<p className="text-gray-700">
We'll call you within <strong>1530 minutes</strong> during business hours.
</p>
</div>
);
}
return (
<div className={`bg-white rounded-lg shadow-lg p-6 ${compact ? 'max-w-sm' : 'max-w-lg'} mx-auto`}>
<div className="flex items-center justify-center mb-6">
<Image
src="/images/favicon.png"
alt="C & I Electrical Contractors"
width={32}
height={32}
className="mr-2"
/>
<span className="text-lg font-semibold text-gray-800">Get Free Quote</span>
</div>
<form onSubmit={submit} noValidate className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="name" className={`block text-sm font-medium ${labelColor} mb-1`}>
Name *
</label>
<input
type="text"
id="name"
name="name"
required
className={`w-full px-3 py-2 border rounded focus:outline-none focus:ring-1 focus:ring-green-500 text-sm ${
errors.name ? 'border-red-500' : 'border-gray-300'
}`}
aria-invalid={!!errors.name}
aria-describedby={errors.name ? 'name-error' : undefined}
/>
{errors.name && (
<p id="name-error" className="mt-1 text-xs text-red-500">
{errors.name}
</p>
)}
</div>
<div>
<label htmlFor="phone" className={`block text-sm font-medium ${labelColor} mb-1`}>
Phone *
</label>
<input
type="tel"
id="phone"
name="phone"
required
className={`w-full px-3 py-2 border rounded focus:outline-none focus:ring-1 focus:ring-green-500 text-sm ${
errors.phone ? 'border-red-500' : 'border-gray-300'
}`}
aria-invalid={!!errors.phone}
aria-describedby={errors.phone ? 'phone-error' : undefined}
/>
{errors.phone && (
<p id="phone-error" className="mt-1 text-xs text-red-500">
{errors.phone}
</p>
)}
</div>
</div>
<div>
<label htmlFor="email" className={`block text-sm font-medium ${labelColor} mb-1`}>
Email *
</label>
<input
type="email"
id="email"
name="email"
required
className={`w-full px-3 py-2 border rounded focus:outline-none focus:ring-1 focus:ring-green-500 text-sm ${
errors.email ? 'border-red-500' : 'border-gray-300'
}`}
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && (
<p id="email-error" className="mt-1 text-xs text-red-500">
{errors.email}
</p>
)}
</div>
<div>
<label htmlFor="serviceType" className={`block text-sm font-medium ${labelColor} mb-1`}>
Service Type *
</label>
<select
id="serviceType"
name="serviceType"
required
className={`w-full px-3 py-2 border rounded focus:outline-none focus:ring-1 focus:ring-green-500 text-sm ${
errors.serviceType ? 'border-red-500' : 'border-gray-300'
}`}
aria-invalid={!!errors.serviceType}
aria-describedby={errors.serviceType ? 'serviceType-error' : undefined}
>
<option value="">Select service type</option>
<option value="emergency">Emergency Repair</option>
<option value="panel-upgrade">Panel Upgrade</option>
<option value="lighting">Lighting & Fixtures</option>
<option value="ev-charging">EV Charging Station</option>
<option value="commercial">Commercial Work</option>
<option value="other">Other</option>
</select>
{errors.serviceType && (
<p id="serviceType-error" className="mt-1 text-xs text-red-500">
{errors.serviceType}
</p>
)}
</div>
<div>
<label htmlFor="issue" className={`block text-sm font-medium ${labelColor} mb-1`}>
Brief Issue Description *
</label>
<textarea
id="issue"
name="issue"
rows={3}
required
className={`w-full px-3 py-2 border-2 border-green-500 rounded focus:outline-none focus:ring-1 focus:ring-green-500 text-sm ${
errors.issue ? 'border-red-500' : 'border-green-500'
}`}
placeholder="Describe your electrical problem or project..."
aria-invalid={!!errors.issue}
aria-describedby={errors.issue ? 'issue-error' : undefined}
/>
{errors.issue && (
<p id="issue-error" className="mt-1 text-xs text-red-500">
{errors.issue}
</p>
)}
</div>
<div>
<label htmlFor="preferredTime" className={`block text-sm font-medium ${labelColor} mb-1`}>
Preferred Time *
</label>
<select
id="preferredTime"
name="preferredTime"
required
className={`w-full px-3 py-2 border rounded focus:outline-none focus:ring-1 focus:ring-green-500 text-sm ${
errors.preferredTime ? 'border-red-500' : 'border-gray-300'
}`}
aria-invalid={!!errors.preferredTime}
aria-describedby={errors.preferredTime ? 'preferredTime-error' : undefined}
>
<option value="">Select preferred time</option>
<option value="emergency">Emergency (same day)</option>
<option value="morning">Morning (7AM-12PM)</option>
<option value="afternoon">Afternoon (12PM-5PM)</option>
<option value="evening">Evening (5PM-8PM)</option>
<option value="flexible">Flexible</option>
</select>
{errors.preferredTime && (
<p id="preferredTime-error" className="mt-1 text-xs text-red-500">
{errors.preferredTime}
</p>
)}
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-3 px-4 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? 'Sending...' : 'Get My Free Quote'}
</button>
</form>
</div>
);
}