Integrating Contact Forms
This guide shows how to integrate contact forms and tour requests into your frontend application.
Contact Form Integration
Section titled “Contact Form Integration”React Example
Section titled “React Example”import { useState } from 'react';
interface ContactFormData { firstName: string; lastName: string; email: string; phone?: string; subject: string; message: string;}
function ContactForm() { const [formData, setFormData] = useState<ContactFormData>({ firstName: '', lastName: '', email: '', phone: '', subject: '', message: '', });
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle'); const [errorMessage, setErrorMessage] = useState('');
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setStatus('submitting'); setErrorMessage('');
try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ firstName: formData.firstName, lastName: formData.lastName, email: formData.email, phone: formData.phone || undefined, subject: formData.subject, message: formData.message, source_page: window.location.pathname, // Include UTM parameters if present utm_source: new URLSearchParams(window.location.search).get('utm_source') || undefined, utm_medium: new URLSearchParams(window.location.search).get('utm_medium') || undefined, utm_campaign: new URLSearchParams(window.location.search).get('utm_campaign') || undefined, }), });
if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to submit form'); }
setStatus('success'); setFormData({ firstName: '', lastName: '', email: '', phone: '', subject: '', message: '', }); } catch (err) { setStatus('error'); setErrorMessage(err instanceof Error ? err.message : 'An error occurred'); } };
return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="firstName">First Name</label> <input type="text" id="firstName" required value={formData.firstName} onChange={(e) => setFormData({ ...formData, firstName: e.target.value })} /> </div>
<div> <label htmlFor="lastName">Last Name</label> <input type="text" id="lastName" required value={formData.lastName} onChange={(e) => setFormData({ ...formData, lastName: e.target.value })} /> </div>
<div> <label htmlFor="email">Email</label> <input type="email" id="email" required value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} /> </div>
<div> <label htmlFor="phone">Phone (optional)</label> <input type="tel" id="phone" value={formData.phone} onChange={(e) => setFormData({ ...formData, phone: e.target.value })} /> </div>
<div> <label htmlFor="subject">Subject</label> <input type="text" id="subject" required value={formData.subject} onChange={(e) => setFormData({ ...formData, subject: e.target.value })} /> </div>
<div> <label htmlFor="message">Message</label> <textarea id="message" required rows={5} value={formData.message} onChange={(e) => setFormData({ ...formData, message: e.target.value })} /> </div>
<button type="submit" disabled={status === 'submitting'}> {status === 'submitting' ? 'Sending...' : 'Send Message'} </button>
{status === 'success' && ( <div className="success"> Thank you! We'll get back to you within 24 hours. </div> )}
{status === 'error' && ( <div className="error"> {errorMessage} </div> )} </form> );}Vanilla JavaScript Example
Section titled “Vanilla JavaScript Example”document.getElementById('contactForm').addEventListener('submit', async (e) => { e.preventDefault();
const formData = new FormData(e.target); const data = { firstName: formData.get('firstName'), lastName: formData.get('lastName'), email: formData.get('email'), phone: formData.get('phone') || undefined, subject: formData.get('subject'), message: formData.get('message'), source_page: window.location.pathname, };
try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), });
if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to submit'); }
// Show success message document.getElementById('successMessage').style.display = 'block'; e.target.reset(); } catch (err) { // Show error message document.getElementById('errorMessage').textContent = err.message; document.getElementById('errorMessage').style.display = 'block'; }});Tour Request Integration
Section titled “Tour Request Integration”Property Detail Page Example
Section titled “Property Detail Page Example”import { useState } from 'react';
interface TourRequestData { listing_id: string; name: string; email: string; phone?: string; preferred_date?: string; preferred_time?: string; message?: string;}
function TourRequestForm({ listingId }: { listingId: string }) { const [formData, setFormData] = useState<TourRequestData>({ listing_id: listingId, name: '', email: '', phone: '', preferred_date: '', preferred_time: '', message: '', });
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setStatus('submitting');
try { const response = await fetch('/api/tours', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ listing_id: formData.listing_id, name: formData.name, email: formData.email, phone: formData.phone || undefined, preferred_date: formData.preferred_date || undefined, preferred_time: formData.preferred_time || undefined, message: formData.message || undefined, }), });
if (!response.ok) { throw new Error('Failed to submit tour request'); }
setStatus('success'); } catch (err) { setStatus('error'); } };
if (status === 'success') { return ( <div className="success-message"> <h3>Tour Request Submitted!</h3> <p>We'll contact you shortly to confirm your tour.</p> </div> ); }
return ( <form onSubmit={handleSubmit}> <h3>Request a Tour</h3>
<div> <label htmlFor="name">Name</label> <input type="text" id="name" required value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} /> </div>
<div> <label htmlFor="email">Email</label> <input type="email" id="email" required value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} /> </div>
<div> <label htmlFor="phone">Phone</label> <input type="tel" id="phone" value={formData.phone} onChange={(e) => setFormData({ ...formData, phone: e.target.value })} /> </div>
<div> <label htmlFor="preferred_date">Preferred Date</label> <input type="date" id="preferred_date" value={formData.preferred_date} onChange={(e) => setFormData({ ...formData, preferred_date: e.target.value })} /> </div>
<div> <label htmlFor="preferred_time">Preferred Time</label> <input type="time" id="preferred_time" value={formData.preferred_time} onChange={(e) => setFormData({ ...formData, preferred_time: e.target.value })} /> </div>
<div> <label htmlFor="message">Message (optional)</label> <textarea id="message" rows={3} value={formData.message} onChange={(e) => setFormData({ ...formData, message: e.target.value })} /> </div>
<button type="submit" disabled={status === 'submitting'}> {status === 'submitting' ? 'Submitting...' : 'Request Tour'} </button>
{status === 'error' && ( <div className="error"> Failed to submit request. Please try again. </div> )} </form> );}Tracking UTM Parameters
Section titled “Tracking UTM Parameters”Capture marketing campaign data:
// Extract UTM parameters from URLfunction getUTMParams() { const params = new URLSearchParams(window.location.search);
return { utm_source: params.get('utm_source') || undefined, utm_medium: params.get('utm_medium') || undefined, utm_campaign: params.get('utm_campaign') || undefined, };}
// Include in form submissionconst submitData = { ...formData, ...getUTMParams(), source_page: window.location.pathname,};Error Handling
Section titled “Error Handling”Validation Errors
Section titled “Validation Errors”Handle 422 Unprocessable Entity responses:
try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), });
if (response.status === 422) { const error = await response.json();
// Extract field-specific errors const fieldErrors: Record<string, string> = {};
error.detail.forEach((err: any) => { const field = err.loc[err.loc.length - 1]; // Last element is field name fieldErrors[field] = err.msg; });
// Display errors next to form fields setErrors(fieldErrors); return; }
if (!response.ok) { throw new Error('Submission failed'); }
// Success} catch (err) { setGeneralError('An unexpected error occurred');}Domain Not Configured
Section titled “Domain Not Configured”If testing on an unmapped domain:
{ "detail": "Domain 'localhost' is not configured. Unable to process lead."}Solution: Use X-Forwarded-Host header in development, or configure a test domain mapping.
cURL Examples
Section titled “cURL Examples”Contact Form
Section titled “Contact Form”curl -X POST \ -H "Content-Type: application/json" \ -d '{ "firstName": "John", "lastName": "Smith", "email": "john@example.com", "phone": "208-555-0123", "subject": "Interested in 123 Main St", "message": "I would like more information about this property." }' \ https://www.your-domain.com/api/contactTour Request
Section titled “Tour Request”curl -X POST \ -H "Content-Type: application/json" \ -d '{ "listing_id": "202412345", "name": "Jane Doe", "email": "jane@example.com", "preferred_date": "2024-12-30", "preferred_time": "10:00 AM" }' \ https://www.your-domain.com/api/toursRelated
Section titled “Related”- Leads API Reference — Endpoint specifications
- Multi-Tenant Routing — How brokerage detection works
- API Getting Started — API basics