// src/hooks/useProfileAddress.ts import { useState, useEffect, useCallback } from 'react'; import toast from 'react-hot-toast'; import type { Address, UserProfile } from '../types'; import { useUserAddressQuery } from './queries/useUserAddressQuery'; import { useGeocodeMutation } from './mutations/useGeocodeMutation'; import { logger } from '../services/logger.client'; import { useDebounce } from './useDebounce'; import { notifyError } from '../services/notificationService'; /** * Helper to generate a consistent address string for geocoding. */ const getAddressString = (address: Partial
): string => { return [ address.address_line_1, address.city, address.province_state, address.postal_code, address.country, ] .filter(Boolean) .join(', '); }; /** * A custom hook to manage a user's profile address, including fetching, * updating, and automatic/manual geocoding. * * Refactored to use TanStack Query (ADR-0005 Phase 7). * * @param userProfile The user's profile object. * @param isOpen Whether the parent component (e.g., a modal) is open. This is used to reset state. * @returns An object with address state and handler functions. */ export const useProfileAddress = (userProfile: UserProfile | null, isOpen: boolean) => { const [address, setAddress] = useState>({}); const [initialAddress, setInitialAddress] = useState>({}); // TanStack Query for fetching the address const { data: fetchedAddress, isLoading: isFetchingAddress, error: addressError, } = useUserAddressQuery(userProfile?.address_id, isOpen && !!userProfile?.address_id); // TanStack Query mutation for geocoding const geocodeMutation = useGeocodeMutation(); // Effect to handle address fetch errors useEffect(() => { if (addressError) { notifyError(addressError.message || 'Failed to fetch address'); } }, [addressError]); // Effect to sync fetched address to local state useEffect(() => { if (!isOpen || !userProfile) { logger.debug( '[useProfileAddress] Modal is closed or profile is null. Resetting address state.', ); setAddress({}); setInitialAddress({}); return; } if (fetchedAddress) { logger.debug('[useProfileAddress] Successfully fetched address:', fetchedAddress); setAddress(fetchedAddress); setInitialAddress(fetchedAddress); } else if (!userProfile.address_id) { logger.debug('[useProfileAddress] Profile has no address_id. Resetting address form.'); setAddress({}); setInitialAddress({}); } else if (!isFetchingAddress && !fetchedAddress && userProfile.address_id) { // Fetch completed but returned null - log a warning logger.warn( `[useProfileAddress] Fetch returned null for addressId: ${userProfile.address_id}.`, ); } }, [isOpen, userProfile, fetchedAddress, isFetchingAddress]); const handleAddressChange = useCallback((field: keyof Address, value: string) => { setAddress((prev) => ({ ...prev, [field]: value })); }, []); const handleManualGeocode = useCallback(async () => { const addressString = getAddressString(address); if (!addressString) { toast.error('Please fill in the address fields before geocoding.'); return; } logger.debug(`[useProfileAddress] Manual geocode triggering for: ${addressString}`); try { const result = await geocodeMutation.mutateAsync(addressString); if (result) { const { lat, lng } = result; setAddress((prev) => ({ ...prev, latitude: lat, longitude: lng })); toast.success('Address re-geocoded successfully!'); } } catch (error) { // Error is already logged by the mutation, but we could show a toast here if needed logger.error('[useProfileAddress] Manual geocode failed:', error); } }, [address, geocodeMutation]); // --- Automatic Geocoding Logic --- const debouncedAddress = useDebounce(address, 1500); useEffect(() => { const handleAutoGeocode = async () => { logger.debug('[useProfileAddress] Auto-geocode effect triggered by debouncedAddress change'); if (JSON.stringify(debouncedAddress) === JSON.stringify(initialAddress)) { logger.debug( '[useProfileAddress] Skipping auto-geocode: address is unchanged from initial load.', ); return; } const addressString = getAddressString(debouncedAddress); // Don't geocode an empty address or if we already have coordinates. if (!addressString || (debouncedAddress.latitude && debouncedAddress.longitude)) { logger.debug( '[useProfileAddress] Skipping auto-geocode: empty string or coordinates already exist', { hasString: !!addressString, hasCoords: !!debouncedAddress.latitude }, ); return; } logger.debug(`[useProfileAddress] Auto-geocoding: "${addressString}"`); try { const result = await geocodeMutation.mutateAsync(addressString); if (result) { logger.debug('[useProfileAddress] Auto-geocode API returned result:', result); const { lat, lng } = result; setAddress((prev) => ({ ...prev, latitude: lat, longitude: lng })); toast.success('Address geocoded successfully!'); } } catch (error) { // Error handling - auto-geocode failures are logged but don't block the user logger.warn('[useProfileAddress] Auto-geocode failed:', error); } }; handleAutoGeocode(); }, [debouncedAddress, initialAddress, geocodeMutation]); return { address, initialAddress, isGeocoding: geocodeMutation.isPending, isFetchingAddress, handleAddressChange, handleManualGeocode, }; };