import { useEffect, useMemo, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { getOriginAddressesPath } from 'data/routes'; import type { LocationResponse } from 'types'; interface UseOriginAddressResult { isResolving: boolean; error: Error | null; origin: Partial< LocationResponse > | null; } /** * Raw origin row from GET /wcshipping/v1/address/origins. Includes * fields needed by both rate requests and purchase persistence. */ interface RawOriginAddress { id?: string; company?: string; name?: string; first_name?: string; last_name?: string; phone?: string; address_1?: string; address_2?: string; city?: string; state?: string; postcode?: string; country?: string; default_address?: boolean; is_verified?: boolean; } export const toOrigin = ( raw: RawOriginAddress ): Partial< LocationResponse > => { const trimmedName = raw.name?.trim(); const name = trimmedName && trimmedName.length > 0 ? trimmedName : [ raw.first_name, raw.last_name ] .map( ( part ) => part?.trim() ) .filter( Boolean ) .join( ' ' ); return { id: raw.id, company: raw.company, name, phone: raw.phone, address_1: raw.address_1, address_2: raw.address_2, city: raw.city, state: raw.state, postcode: raw.postcode, country: raw.country, is_verified: raw.is_verified, }; }; /** * Fetch the store's ship-from address once when the modal opens. The * batch rate endpoint quotes the whole batch from a single origin, so we * pick the merchant's default origin (falling back to the first one). */ export const useOriginAddress = ( enabled: boolean ): UseOriginAddressResult => { const [ origin, setOrigin ] = useState< Partial< LocationResponse > | null >( null ); const [ isResolving, setIsResolving ] = useState< boolean >( enabled ); const [ error, setError ] = useState< Error | null >( null ); useEffect( () => { if ( ! enabled ) { return; } let cancelled = false; setIsResolving( true ); setError( null ); apiFetch< RawOriginAddress[] >( { path: getOriginAddressesPath() } ) .then( ( response ) => { if ( cancelled ) { return; } const list = Array.isArray( response ) ? response : []; // Match the single-label rate flow: only quote from a // verified origin. If none is verified, block the quote // (origin stays null) and surface why, rather than // silently rating against an address the other flow // would reject. const verified = list.filter( ( row ) => row.is_verified ); const picked = verified.find( ( row ) => row.default_address ) ?? verified[ 0 ] ?? null; if ( ! picked ) { setOrigin( null ); setError( list.length > 0 ? new Error( __( 'No verified ship-from address. Verify an origin address in settings to quote rates.', 'woocommerce-shipping' ) ) : null ); setIsResolving( false ); return; } setOrigin( toOrigin( picked ) ); setIsResolving( false ); } ) .catch( ( err: unknown ) => { if ( cancelled ) { return; } setOrigin( null ); setError( err instanceof Error ? err : new Error( 'Failed to load the ship-from address.' ) ); setIsResolving( false ); } ); return () => { cancelled = true; }; }, [ enabled ] ); return useMemo( () => ( { isResolving, error, origin } ), [ isResolving, error, origin ] ); };