mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 00:51:12 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
316
src-old/components/contact/ContactForm.tsx
Normal file
316
src-old/components/contact/ContactForm.tsx
Normal file
@@ -0,0 +1,316 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Send } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { TurnstileCaptcha } from '@/components/auth/TurnstileCaptcha';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { handleError, handleSuccess } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { contactFormSchema, contactCategories, type ContactFormData } from '@/lib/contactValidation';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
|
||||
export function ContactForm() {
|
||||
const { user } = useAuth();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [captchaToken, setCaptchaToken] = useState<string>('');
|
||||
const [captchaKey, setCaptchaKey] = useState(0);
|
||||
const [userName, setUserName] = useState('');
|
||||
const [userEmail, setUserEmail] = useState('');
|
||||
|
||||
// Fetch user profile if logged in
|
||||
useEffect(() => {
|
||||
const fetchUserProfile = async () => {
|
||||
if (user) {
|
||||
setUserEmail(user.email || '');
|
||||
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('display_name, username')
|
||||
.eq('user_id', user.id)
|
||||
.single();
|
||||
|
||||
if (profile) {
|
||||
setUserName(profile.display_name || profile.username || '');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchUserProfile();
|
||||
}, [user]);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm<ContactFormData>({
|
||||
resolver: zodResolver(contactFormSchema),
|
||||
defaultValues: {
|
||||
name: userName,
|
||||
email: userEmail,
|
||||
subject: '',
|
||||
category: 'general',
|
||||
message: '',
|
||||
captchaToken: '',
|
||||
},
|
||||
});
|
||||
|
||||
// Update form when user data loads
|
||||
useEffect(() => {
|
||||
if (userName) {
|
||||
setValue('name', userName);
|
||||
}
|
||||
if (userEmail) {
|
||||
setValue('email', userEmail);
|
||||
}
|
||||
}, [userName, userEmail, setValue]);
|
||||
|
||||
const onCaptchaSuccess = (token: string) => {
|
||||
setCaptchaToken(token);
|
||||
setValue('captchaToken', token);
|
||||
};
|
||||
|
||||
const onCaptchaError = () => {
|
||||
setCaptchaToken('');
|
||||
setValue('captchaToken', '');
|
||||
handleError(
|
||||
new Error('CAPTCHA verification failed'),
|
||||
{ action: 'verify_captcha' }
|
||||
);
|
||||
};
|
||||
|
||||
const onCaptchaExpire = () => {
|
||||
setCaptchaToken('');
|
||||
setValue('captchaToken', '');
|
||||
};
|
||||
|
||||
const onSubmit = async (data: ContactFormData) => {
|
||||
if (!captchaToken) {
|
||||
handleError(
|
||||
new Error('Please complete the CAPTCHA'),
|
||||
{ action: 'submit_contact_form' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
logger.info('Submitting contact form', { category: data.category });
|
||||
|
||||
const { data: result, error } = await supabase.functions.invoke(
|
||||
'send-contact-message',
|
||||
{
|
||||
body: {
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
subject: data.subject,
|
||||
message: data.message,
|
||||
category: data.category,
|
||||
captchaToken: data.captchaToken,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
handleSuccess(
|
||||
'Message Sent!',
|
||||
"Thank you for contacting us. We'll respond within 24-48 hours."
|
||||
);
|
||||
|
||||
// Reset form
|
||||
reset({
|
||||
name: userName,
|
||||
email: userEmail,
|
||||
subject: '',
|
||||
category: 'general',
|
||||
message: '',
|
||||
captchaToken: '',
|
||||
});
|
||||
|
||||
// Reset CAPTCHA
|
||||
setCaptchaToken('');
|
||||
setCaptchaKey((prev) => prev + 1);
|
||||
|
||||
} catch (error) {
|
||||
handleError(error, {
|
||||
action: 'submit_contact_form',
|
||||
metadata: { category: data.category },
|
||||
});
|
||||
|
||||
// Reset CAPTCHA on error
|
||||
setCaptchaToken('');
|
||||
setCaptchaKey((prev) => prev + 1);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||
{/* Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Name *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
{...register('name')}
|
||||
placeholder="Your name"
|
||||
disabled={isSubmitting}
|
||||
className={errors.name ? 'border-destructive' : ''}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-sm text-destructive">{errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email *</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
{...register('email')}
|
||||
placeholder="your.email@example.com"
|
||||
disabled={isSubmitting}
|
||||
className={errors.email ? 'border-destructive' : ''}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-sm text-destructive">{errors.email.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Category */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="category">Category *</Label>
|
||||
<Select
|
||||
value={watch('category')}
|
||||
onValueChange={(value) =>
|
||||
setValue('category', value as ContactFormData['category'])
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<SelectTrigger className={errors.category ? 'border-destructive' : ''}>
|
||||
<SelectValue placeholder="Select a category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contactCategories.map((cat) => (
|
||||
<SelectItem key={cat.value} value={cat.value}>
|
||||
{cat.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.category && (
|
||||
<p className="text-sm text-destructive">{errors.category.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Subject */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="subject">Subject *</Label>
|
||||
<Input
|
||||
id="subject"
|
||||
{...register('subject')}
|
||||
placeholder="Brief description of your inquiry"
|
||||
disabled={isSubmitting}
|
||||
className={errors.subject ? 'border-destructive' : ''}
|
||||
/>
|
||||
{errors.subject && (
|
||||
<p className="text-sm text-destructive">{errors.subject.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="message">Message * (minimum 20 characters)</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
{...register('message')}
|
||||
placeholder="Please provide details about your inquiry (minimum 20 characters)..."
|
||||
rows={6}
|
||||
disabled={isSubmitting}
|
||||
className={errors.message ? 'border-destructive' : ''}
|
||||
/>
|
||||
<div className="flex justify-between items-center">
|
||||
{errors.message && (
|
||||
<p className="text-sm text-destructive">{errors.message.message}</p>
|
||||
)}
|
||||
<p className={`text-sm ml-auto ${
|
||||
(watch('message')?.length || 0) < 20
|
||||
? 'text-destructive font-medium'
|
||||
: 'text-muted-foreground'
|
||||
}`}>
|
||||
{watch('message')?.length || 0} / 2000
|
||||
{(watch('message')?.length || 0) < 20 &&
|
||||
` (${20 - (watch('message')?.length || 0)} more needed)`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CAPTCHA */}
|
||||
<div className="space-y-2">
|
||||
<TurnstileCaptcha
|
||||
key={captchaKey}
|
||||
onSuccess={onCaptchaSuccess}
|
||||
onError={onCaptchaError}
|
||||
onExpire={onCaptchaExpire}
|
||||
theme="auto"
|
||||
size="normal"
|
||||
/>
|
||||
{errors.captchaToken && (
|
||||
<p className="text-sm text-destructive">
|
||||
{errors.captchaToken.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
disabled={isSubmitting || !captchaToken || (watch('message')?.length || 0) < 20}
|
||||
className="w-full"
|
||||
title={
|
||||
!captchaToken
|
||||
? 'Please complete the CAPTCHA'
|
||||
: (watch('message')?.length || 0) < 20
|
||||
? `Message must be at least 20 characters (currently ${watch('message')?.length || 0})`
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<span className="animate-spin mr-2">⏳</span>
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Send Message
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
We typically respond within 24-48 hours
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user