import React, { useState, useEffect, useCallback } from "react"; import { Address } from "../types"; import { geocodingService, AddressComponents, } from "../services/geocodingService"; import AddressAutocomplete from "./AddressAutocomplete"; import { PlaceDetails } from "../services/placesService"; import { useAddressAutocomplete, usStates, } from "../hooks/useAddressAutocomplete"; interface LocationFormData { address1: string; address2: string; city: string; state: string; zipCode: string; country: string; latitude?: number; longitude?: number; } interface LocationFormProps { data: LocationFormData; userAddresses: Address[]; selectedAddressId: string; addressesLoading: boolean; onChange: ( e: React.ChangeEvent ) => void; onAddressSelect: (addressId: string) => void; formatAddressDisplay: (address: Address) => string; onCoordinatesChange?: (latitude: number, longitude: number) => void; onGeocodeRef?: ( geocodeFunction: () => Promise<{ latitude: number; longitude: number; } | null> ) => void; } const LocationForm: React.FC = ({ data, userAddresses, selectedAddressId, addressesLoading, onChange, onAddressSelect, formatAddressDisplay, onCoordinatesChange, onGeocodeRef, }) => { const [geocoding, setGeocoding] = useState(false); const [geocodeError, setGeocodeError] = useState(null); const [geocodeSuccess, setGeocodeSuccess] = useState(false); const [placesApiError, setPlacesApiError] = useState(false); // Debounced geocoding function const geocodeAddress = useCallback( async ( addressData: LocationFormData ): Promise<{ latitude: number; longitude: number } | null> => { if ( !geocodingService.isAddressComplete(addressData as AddressComponents) ) { return null; } setGeocoding(true); setGeocodeError(null); setGeocodeSuccess(false); try { const result = await geocodingService.geocodeAddress( addressData as AddressComponents ); if ("error" in result) { setGeocodeError(result.details || result.error); return null; } else { setGeocodeSuccess(true); if (onCoordinatesChange) { onCoordinatesChange(result.latitude, result.longitude); } // Clear success message after 3 seconds setTimeout(() => setGeocodeSuccess(false), 3000); // Return the coordinates return { latitude: result.latitude, longitude: result.longitude }; } } catch (error) { setGeocodeError("Failed to geocode address"); return null; } finally { setGeocoding(false); } }, [onCoordinatesChange] ); // Expose geocoding function to parent components const triggerGeocoding = useCallback(async () => { if (data.address1 && data.city && data.state && data.zipCode) { const coordinates = await geocodeAddress(data); return coordinates; // Return coordinates directly from geocoding } return null; // Incomplete address }, [data, geocodeAddress]); // Pass geocoding function to parent component useEffect(() => { if (onGeocodeRef) { onGeocodeRef(triggerGeocoding); } }, [onGeocodeRef, triggerGeocoding]); // Use address autocomplete hook const { parsePlace } = useAddressAutocomplete(); // Handle place selection from autocomplete const handlePlaceSelect = useCallback( (place: PlaceDetails) => { try { const parsedAddress = parsePlace(place); if (!parsedAddress) { setPlacesApiError(true); return; } // Create synthetic events to update form data const createSyntheticEvent = (name: string, value: string) => ({ target: { name, value, type: "text", }, } as React.ChangeEvent); // Update all address fields onChange(createSyntheticEvent("address1", parsedAddress.address1)); onChange(createSyntheticEvent("city", parsedAddress.city)); onChange(createSyntheticEvent("state", parsedAddress.state)); onChange(createSyntheticEvent("zipCode", parsedAddress.zipCode)); onChange(createSyntheticEvent("country", parsedAddress.country)); // Set coordinates immediately if (onCoordinatesChange) { onCoordinatesChange(parsedAddress.latitude, parsedAddress.longitude); } // Clear any previous geocoding messages setGeocodeError(null); setGeocodeSuccess(true); setPlacesApiError(false); setTimeout(() => setGeocodeSuccess(false), 3000); } catch (error) { console.error("Error handling place selection:", error); setPlacesApiError(true); } }, [onChange, onCoordinatesChange, parsePlace] ); // Handle Places API errors const handlePlacesApiError = useCallback(() => { setPlacesApiError(true); }, []); return (
Your address is private. This will only be used to show renters a general area.
{addressesLoading ? (
Loading addresses...
) : ( <> {/* Multiple addresses - show dropdown */} {userAddresses.length > 1 && (
)} {/* Show form fields for all scenarios with addresses <= 1 or when "new" is selected */} {(userAddresses.length <= 1 || (userAddresses.length > 1 && !selectedAddressId)) && ( <>
{!placesApiError ? ( { const syntheticEvent = { target: { name: "address1", value, type: "text", }, } as React.ChangeEvent; onChange(syntheticEvent); }} onPlaceSelect={handlePlaceSelect} onError={handlePlacesApiError} placeholder="Start typing an address..." className="form-control" required countryRestriction="us" types={["address"]} /> ) : ( )} {placesApiError && (
Address autocomplete is unavailable. Using manual input.
)}
)} )}
); }; export default LocationForm;