Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | 1x 72x 1x 203x 203x 203x 203x 203x 48x 2x 203x 82x 7x 7x 7x 7x 75x 33x 33x 33x 42x 2x 2x 2x 40x 2x 203x 7x 203x 4x 4x 1x 1x 3x 3x 3x 1x 1x 1x 1x 2x 203x 203x 186x 186x 186x 118x 118x 68x 68x 63x 63x 5x 5x 5x 4x 4x 4x 4x 4x 1x 186x 203x | // 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<Address>): 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<Partial<Address>>({});
const [initialAddress, setInitialAddress] = useState<Partial<Address>>({});
// 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);
Eif (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);
Eif (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,
};
};
|