moar fixes + unit test review of routes
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 57m53s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 57m53s
This commit is contained in:
@@ -12,11 +12,11 @@ import { GoogleIcon } from '../../../components/icons/GoogleIcon';
|
||||
import { GithubIcon } from '../../../components/icons/GithubIcon';
|
||||
import { ConfirmationModal } from '../../../components/ConfirmationModal';
|
||||
import { PasswordInput } from './PasswordInput';
|
||||
import { AddressForm } from './AddressForm';
|
||||
import { MapView } from '../../../components/MapView';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import type { AuthStatus } from '../../../hooks/useAuth';
|
||||
import { AuthView } from './AuthView';
|
||||
import { AddressForm } from './AddressForm';
|
||||
import { useProfileAddress } from '../../../hooks/useProfileAddress';
|
||||
|
||||
interface ProfileManagerProps {
|
||||
isOpen: boolean;
|
||||
@@ -34,14 +34,12 @@ interface ProfileManagerProps {
|
||||
// to the signature expected by the useApi hook (which passes a raw AbortSignal).
|
||||
// They are defined outside the component to ensure they have a stable identity
|
||||
// across re-renders, preventing infinite loops in useEffect hooks.
|
||||
const updateProfileWrapper = (data: Partial<Profile>, signal?: AbortSignal) => apiClient.updateUserProfile(data, { signal });
|
||||
const updateAddressWrapper = (data: Partial<Address>, signal?: AbortSignal) => apiClient.updateUserAddress(data, { signal });
|
||||
const geocodeWrapper = (address: string, signal?: AbortSignal) => apiClient.geocodeAddress(address, { signal });
|
||||
const updatePasswordWrapper = (password: string, signal?: AbortSignal) => apiClient.updateUserPassword(password, { signal });
|
||||
const exportDataWrapper = (signal?: AbortSignal) => apiClient.exportUserData({ signal });
|
||||
const deleteAccountWrapper = (password: string, signal?: AbortSignal) => apiClient.deleteUserAccount(password, { signal });
|
||||
const updatePreferencesWrapper = (prefs: Partial<Profile['preferences']>, signal?: AbortSignal) => apiClient.updateUserPreferences(prefs, { signal });
|
||||
const fetchAddressWrapper = (id: number, signal?: AbortSignal) => apiClient.getUserAddress(id, { signal });
|
||||
const updateProfileWrapper = (data: Partial<Profile>, signal?: AbortSignal) => apiClient.updateUserProfile(data, { signal });
|
||||
|
||||
export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose, user, authStatus, profile, onProfileUpdate, onSignOut, onLoginSuccess }) => { // This line had a type error due to syntax issues below.
|
||||
const [activeTab, setActiveTab] = useState('profile');
|
||||
@@ -49,13 +47,12 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
// Profile state
|
||||
const [fullName, setFullName] = useState(profile?.full_name || '');
|
||||
const [avatarUrl, setAvatarUrl] = useState(profile?.avatar_url || '');
|
||||
const [address, setAddress] = useState<Partial<Address>>({});
|
||||
const [initialAddress, setInitialAddress] = useState<Partial<Address>>({}); // Store initial address for comparison
|
||||
|
||||
// Address logic is now encapsulated in this custom hook.
|
||||
const { address, initialAddress, isGeocoding, handleAddressChange, handleManualGeocode } = useProfileAddress(profile, isOpen);
|
||||
|
||||
const { execute: updateProfile, loading: profileLoading } = useApi<Profile, [Partial<Profile>]>(updateProfileWrapper);
|
||||
const { execute: updateAddress, loading: addressLoading } = useApi<Address, [Partial<Address>]>(updateAddressWrapper);
|
||||
const { execute: geocode, loading: isGeocoding } = useApi<{ lat: number; lng: number }, [string]>(geocodeWrapper);
|
||||
|
||||
|
||||
// Password state
|
||||
const [password, setPassword] = useState('');
|
||||
@@ -73,48 +70,19 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
|
||||
const [passwordForDelete, setPasswordForDelete] = useState('');
|
||||
|
||||
// New hook to fetch address details
|
||||
const { execute: fetchAddress } = useApi<Address, [number]>(fetchAddressWrapper);
|
||||
|
||||
const handleAddressFetch = useCallback(async (addressId: number) => {
|
||||
logger.debug(`[handleAddressFetch] Starting fetch for addressId: ${addressId}`);
|
||||
const fetchedAddress = await fetchAddress(addressId);
|
||||
if (fetchedAddress) {
|
||||
logger.debug('[handleAddressFetch] Successfully fetched address:', fetchedAddress);
|
||||
setAddress(fetchedAddress);
|
||||
setInitialAddress(fetchedAddress); // Set initial address on fetch
|
||||
} else {
|
||||
logger.warn(`[handleAddressFetch] Fetch returned null or undefined for addressId: ${addressId}. This might indicate a network error caught by useApi.`);
|
||||
}
|
||||
}, [fetchAddress]);
|
||||
|
||||
useEffect(() => {
|
||||
// Only reset state when the modal is opened.
|
||||
// Do not reset on profile changes, which can happen during sign-out.
|
||||
logger.debug('[useEffect] Running effect due to change in isOpen or profile.', { isOpen, profileExists: !!profile });
|
||||
if (isOpen && profile) { // Ensure profile exists before setting state
|
||||
logger.debug('[useEffect] Modal is open with a valid profile. Resetting component state.');
|
||||
setFullName(profile?.full_name || '');
|
||||
setAvatarUrl(profile?.avatar_url || '');
|
||||
// If the user has an address, fetch its details
|
||||
if (profile.address_id) {
|
||||
logger.debug(`[useEffect] Profile has address_id: ${profile.address_id}. Calling handleAddressFetch.`);
|
||||
handleAddressFetch(profile.address_id);
|
||||
} else {
|
||||
// Reset address form if user has no address
|
||||
logger.debug('[useEffect] Profile has no address_id. Resetting address form.');
|
||||
setAddress({});
|
||||
setInitialAddress({});
|
||||
}
|
||||
setFullName(profile.full_name || '');
|
||||
setAvatarUrl(profile.avatar_url || '');
|
||||
setActiveTab('profile');
|
||||
setIsConfirmingDelete(false);
|
||||
setPasswordForDelete('');
|
||||
} else {
|
||||
logger.debug('[useEffect] Modal is closed or profile is null. Resetting address state only.');
|
||||
setAddress({});
|
||||
setInitialAddress({});
|
||||
}
|
||||
}, [isOpen, profile, handleAddressFetch]); // Depend on isOpen and profile
|
||||
}, [isOpen, profile]); // Depend on isOpen and profile
|
||||
|
||||
const handleProfileSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -203,75 +171,6 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
// This log confirms the function has completed its execution.
|
||||
logger.debug('[handleProfileSave] Save process finished.');
|
||||
};
|
||||
|
||||
// --- DEBUG LOGGING ---
|
||||
// Log the loading states on every render to debug the submit button's disabled state.
|
||||
logger.debug('[ComponentRender] Loading states:', { profileLoading, addressLoading });
|
||||
// Log the function reference itself to see if it's being recreated unexpectedly.
|
||||
// We convert it to a string to see a snapshot in time.
|
||||
logger.debug(`[ComponentRender] handleProfileSave function created.`);
|
||||
|
||||
const handleAddressChange = (field: keyof Address, value: string) => {
|
||||
setAddress(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleManualGeocode = async () => {
|
||||
const addressString = [
|
||||
address.address_line_1,
|
||||
address.city,
|
||||
address.province_state,
|
||||
address.postal_code,
|
||||
address.country,
|
||||
].filter(Boolean).join(', ');
|
||||
|
||||
if (!addressString) {
|
||||
toast.error('Please fill in the address fields before geocoding.');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await geocode(addressString);
|
||||
if (result) {
|
||||
const { lat, lng } = result;
|
||||
setAddress(prev => ({ ...prev, latitude: lat, longitude: lng }));
|
||||
toast.success('Address re-geocoded successfully!');
|
||||
}
|
||||
};
|
||||
// --- Automatic Geocoding Logic ---
|
||||
const debouncedAddress = useDebounce(address, 1500); // Debounce address state by 1.5 seconds
|
||||
|
||||
useEffect(() => {
|
||||
// This effect runs when the debouncedAddress value changes.
|
||||
const handleGeocode = async () => {
|
||||
logger.debug('[handleGeocode] Effect triggered by debouncedAddress change');
|
||||
// Only trigger if the core address fields are present and have changed.
|
||||
const addressString = [
|
||||
debouncedAddress.address_line_1,
|
||||
debouncedAddress.city,
|
||||
debouncedAddress.province_state,
|
||||
debouncedAddress.postal_code,
|
||||
debouncedAddress.country,
|
||||
].filter(Boolean).join(', ');
|
||||
|
||||
logger.debug(`[handleGeocode] addressString generated: "${addressString}"`);
|
||||
|
||||
// Don't geocode an empty address or if we already have coordinates for this exact address.
|
||||
if (!addressString || (debouncedAddress.latitude && debouncedAddress.longitude)) {
|
||||
logger.debug('[handleGeocode] Skipping geocode: empty string or coordinates already exist');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug('[handleGeocode] Calling geocode API...');
|
||||
const result = await geocode(addressString);
|
||||
if (result) {
|
||||
logger.debug('[handleGeocode] API returned result:', result);
|
||||
const { lat, lng } = result;
|
||||
setAddress(prev => ({ ...prev, latitude: lat, longitude: lng }));
|
||||
toast.success('Address geocoded successfully!');
|
||||
}
|
||||
};
|
||||
|
||||
handleGeocode();
|
||||
}, [debouncedAddress]); // Dependency array ensures this runs only when the debounced value changes.
|
||||
|
||||
const handleOAuthLink = async (provider: 'google' | 'github') => {
|
||||
// This will redirect the user to the OAuth provider to link the account.
|
||||
|
||||
Reference in New Issue
Block a user