import React, { useState, useCallback, useEffect, useRef } from 'react'; import { createPortal } from 'react-dom'; import { TravelEnquiryFormProps, FormData } from './types'; import { FormOverlay, FormContainer, CloseButton, FormTitle, FormSubtitle, ButtonRow, NavButton, FormRequiredNote } from './styles'; import { INITIAL_FORM_DATA } from './constants'; import { validateStep, trackEnquiryEvent } from './utils'; import { submitEnquiry, ApiError } from './submitEnquiry'; import { StepIndicator } from './StepIndicator'; import { PersonalDetailsStep } from './PersonalDetailsStep'; import { TripDetailsStep } from './TripDetailsStep'; import { BudgetExtrasStep } from './BudgetExtrasStep'; import { SuccessScreen } from './SuccessScreen'; import { InlineMessage } from './InlineMessage'; export const TravelEnquiryForm: React.FC = ({ isApp, onClose }) => { const [currentStep, setCurrentStep] = useState(1); const [formData, setFormData] = useState(INITIAL_FORM_DATA); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const [submitSuccess, setSubmitSuccess] = useState(false); const [submitError, setSubmitError] = useState(''); const visitedSteps = useRef(new Set([1])); const containerRef = useRef(null); const trackedSteps = useRef(new Set()); useEffect(() => { const originalOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = originalOverflow; }; }, []); /* ---------- step view tracking ---------- */ useEffect( () => { if (trackedSteps.current.has(currentStep)) { return; } trackedSteps.current.add(currentStep); trackEnquiryEvent( `enquiry form : step ${currentStep} : display`, 'automated' ); }, [currentStep] ); const updateField = useCallback( (field: keyof FormData, value: string | number | boolean) => { setFormData(prev => ({ ...prev, [field]: value })); setErrors(prev => { if (prev[field]) { const next = { ...prev }; delete next[field]; return next; } return prev; }); }, [] ); /* ---------- navigation ---------- */ const scrollToTop = () => { if (containerRef.current && containerRef.current.scrollTo) { containerRef.current.scrollTo(0, 0); } }; const handleNext = () => { const { isValid, errors: stepErrors } = validateStep(currentStep, formData); if (!isValid) { setErrors(stepErrors); Object.values(stepErrors).forEach(message => { trackEnquiryEvent( `enquiry form : step ${currentStep} : error message displayed`, 'automated', message ); }); return; } trackEnquiryEvent(`enquiry form : step ${currentStep} : next`, 'click'); setErrors({}); const nextStep = Math.min(currentStep + 1, 3); const isFirstVisit = !visitedSteps.current.has(nextStep); visitedSteps.current.add(nextStep); setCurrentStep(nextStep); if (isFirstVisit) { scrollToTop(); } }; const handleBack = () => { trackEnquiryEvent(`enquiry form : step ${currentStep} : back`, 'click'); setErrors({}); setCurrentStep(prev => Math.max(prev - 1, 1)); }; /* ---------- submission ---------- */ const handleSubmit = async () => { const { isValid, errors: stepErrors } = validateStep(3, formData); if (!isValid) { setErrors(stepErrors); Object.values(stepErrors).forEach(message => { trackEnquiryEvent( `enquiry form : step ${currentStep} : error message displayed`, 'automated', message ); }); return; } trackEnquiryEvent( `enquiry form : step ${currentStep} : submit enquiry`, 'click' ); setIsSubmitting(true); setSubmitError(''); try { await submitEnquiry(formData); trackEnquiryEvent('enquiry form : submitted', 'automated'); setSubmitSuccess(true); } catch (error) { // tslint:disable-next-line:no-console console.error('Form submission error:', error); const errorMessage = error instanceof ApiError ? error.message : 'Please try again or call us on'; trackEnquiryEvent( `enquiry form : step ${currentStep} : error message displayed`, 'automated', 'Something went wrong when submitting your enquiry' ); setSubmitError(errorMessage); } finally { setIsSubmitting(false); } }; /* ---------- enter key → next/submit ---------- */ const handleFormSubmit = (e: React.FormEvent) => { e.preventDefault(); if (currentStep < 3) { handleNext(); } else { handleSubmit(); } }; if (submitSuccess) { return ; } /* ---------- step props ---------- */ const stepProps = { isApp, formData, errors, updateField }; /* ---------- main form ---------- */ return createPortal( e.stopPropagation()} data-testid="travel-enquiry-form" > ✕
Enquiry form Fill in the form below to describe what you want from your next trip. We'll get back to you as soon as possible. * required field {currentStep === 1 && } {currentStep === 2 && } {currentStep === 3 && } {submitError && ( )} {currentStep > 1 ? ( Back ) : null} {currentStep < 3 ? ( Next ) : ( {isSubmitting ? 'Submitting...' : 'Submit enquiry'} )}
, document.body ); };