import { createReducer, getCurrentOrderShipTo, getFirstSelectableOriginAddress, getIsDestinationVerified, getOriginAddresses, getStoreOrigin, isOriginAddress, } from 'utils'; import { getFormattedStoreAddress, isMainOriginInSyncWithStore, getStoreAddressDraft, } from 'utils/config'; import { ADD_ORIGIN_ADDRESS, ADD_ORIGIN_ADDRESS_FAILED, ADDRESS_NORMALIZATION, ADDRESS_NORMALIZATION_FAILED, DELETE_ORIGIN_ADDRESS, FETCH_ORIGIN_ADDRESSES, RESET_ADDRESS_NORMALIZATION, SET_DESTINATION_RESIDENTIAL, SET_STORE_ADDRESS_SYNC_STATUS, STATE_RESET, UPDATE_SHIPMENT_ADDRESS, UPDATE_SHIPMENT_ADDRESS_FAILED, VERIFY_ORDER_SHIPPING_ADDRESS, VERIFY_ORDER_SHIPPING_ADDRESS_FAILED, VERIFY_ORDER_SHIPPING_ADDRESS_START, } from './action-types'; import { AddOriginAddressAction, AddOriginAddressFailedAction, AddressActions, DeleteOriginAddressAction, FetchOriginAddressesAction, NormalizationAddressAction, NormalizationAddressFailedAction, SetDestinationResidentialAction, SetStoreAddressSyncStatusAction, ShippingAddressVerifyAction, ShippingAddressVerifyFailedAction, ShippingAddressVerifyStartAction, UpdateShipmentAddressAction, UpdateShipmentAddressFailedAction, } from './types.d'; import { resetAddressNormalizationResponse } from './actions'; import { AddressState } from '../types'; export const getReducer = ( withDestination: boolean ) => { const getDefaultState = (): AddressState => { const originAddresses = getOriginAddresses(); const firstSelectableAddress = getFirstSelectableOriginAddress(); const storeAddressDraft = getStoreAddressDraft(); // Provide a default empty address if none exist const defaultOriginAddress = firstSelectableAddress ?? { id: '', name: '', address: '', address1: '', address2: '', city: '', state: '', postcode: '', country: getStoreOrigin().country, phone: '', email: '', firstName: '', lastName: '', company: '', isVerified: false, defaultAddress: false, }; const defaultState: AddressState = { origin: { addresses: originAddresses, address: defaultOriginAddress, isVerified: defaultOriginAddress.isVerified ?? false, isAddressVerificationInProgress: false, normalizedAddress: null, submittedAddress: null, isTrivialNormalization: null, addressNeedsConfirmation: false, formErrors: {}, }, storeOrigin: getStoreOrigin(), isMainOriginInSyncWithStore: isMainOriginInSyncWithStore(), formattedStoreAddress: getFormattedStoreAddress(), storeAddressDraft, } as const; if ( withDestination ) { defaultState.destination = { address: getCurrentOrderShipTo(), isVerified: getIsDestinationVerified(), isAddressVerificationInProgress: false, normalizedAddress: null, submittedAddress: null, isTrivialNormalization: null, addressNeedsConfirmation: false, formErrors: {}, }; } return defaultState; }; const addressReducer = createReducer( getDefaultState() ) .on( ADDRESS_NORMALIZATION, ( state, { payload: { address, normalizedAddress, isTrivialNormalization, addressType, warnings, }, }: NormalizationAddressAction ) => ( { ...state, [ addressType ]: { ...state[ addressType ], normalizedAddress, isTrivialNormalization, addressNeedsConfirmation: true, submittedAddress: address, formErrors: {}, isAddressVerificationInProgress: false, warnings, }, } ) ) .on( ADDRESS_NORMALIZATION_FAILED, ( state, { payload }: NormalizationAddressFailedAction ) => { const normalizationErrors: Record< string, string > = { ...( payload.errors ?? {} ), }; if ( payload.errors?.general ) { normalizationErrors.general = payload.errors.general; } if ( payload.message ) { normalizationErrors.general = payload.message; } return { ...state, [ payload.addressType ]: { ...state[ payload.addressType ], addressNeedsConfirmation: false, normalizedAddress: null, submittedAddress: payload.address, formErrors: normalizationErrors, isAddressVerificationInProgress: false, }, }; } ) .on( RESET_ADDRESS_NORMALIZATION, ( state, { payload: { addressType }, }: ReturnType< typeof resetAddressNormalizationResponse > ) => ( { ...state, [ addressType ]: { ...state[ addressType ], addressNeedsConfirmation: false, }, } ) ) .on( UPDATE_SHIPMENT_ADDRESS, ( state, { payload: { address, isVerified, addressType }, }: UpdateShipmentAddressAction ) => { let addressesMerger = {}; if ( addressType === 'origin' && isOriginAddress( address ) ) { const addresses = state.origin.addresses.map( ( originAddress ) => { if ( originAddress.id === address.id ) { return address; } // Clear defaultAddress from other addresses if the updated address is being set as default if ( address.defaultAddress && originAddress.defaultAddress ) { return { ...originAddress, defaultAddress: false, }; } // Clear defaultReturnAddress from other addresses if the updated address is being set as default return if ( address.defaultReturnAddress && originAddress.defaultReturnAddress ) { return { ...originAddress, defaultReturnAddress: false, }; } return originAddress; } ); addressesMerger = { addresses }; } return { ...state, [ addressType ]: { ...state[ addressType ], formErrors: {}, isVerified, address, addressNeedsConfirmation: false, isAddressVerificationInProgress: false, ...addressesMerger, }, }; } ) .on( UPDATE_SHIPMENT_ADDRESS_FAILED, ( state, { payload: { addressType, message }, }: UpdateShipmentAddressFailedAction ) => ( { ...state, [ addressType ]: { ...state[ addressType ], isVerified: false, addressNeedsConfirmation: false, formErrors: { general: message }, isAddressVerificationInProgress: false, }, } ) ) .on( ADD_ORIGIN_ADDRESS, ( state, { payload: { address } }: AddOriginAddressAction ) => { let existingAddresses = state.origin.addresses; // Clear defaultAddress from other addresses if the new address is being set as default if ( address.defaultAddress ) { existingAddresses = existingAddresses.map( ( origin ) => ( { ...origin, defaultAddress: false, } ) ); } // Clear defaultReturnAddress from other addresses if the new address is being set as default return if ( address.defaultReturnAddress ) { existingAddresses = existingAddresses.map( ( origin ) => ( { ...origin, defaultReturnAddress: false, } ) ); } return { ...state, origin: { ...state.origin, formErrors: {}, isVerified: true, address, addressNeedsConfirmation: false, addresses: [ ...existingAddresses, address ], }, }; } ) .on( ADD_ORIGIN_ADDRESS_FAILED, ( state, { payload: { error } }: AddOriginAddressFailedAction ) => { return { ...state, origin: { ...state.origin, formErrors: error, }, }; } ) .on( VERIFY_ORDER_SHIPPING_ADDRESS_START, ( state, { payload: { addressType } }: ShippingAddressVerifyStartAction ) => { return { ...state, [ addressType ]: { ...state[ addressType ], isAddressVerificationInProgress: true, }, }; } ) .on( VERIFY_ORDER_SHIPPING_ADDRESS, ( state, { payload: { normalizedAddress, isTrivialNormalization, isVerified, addressType, }, }: ShippingAddressVerifyAction ) => ( { ...state, [ addressType ]: { ...state[ addressType ], isVerified, normalizedAddress, isTrivialNormalization, isAddressVerificationInProgress: false, }, } ) ) .on( VERIFY_ORDER_SHIPPING_ADDRESS_FAILED, ( state, { payload: { addressType } }: ShippingAddressVerifyFailedAction ) => { return { ...state, [ addressType ]: { ...state[ addressType ], isVerified: false, isAddressVerificationInProgress: false, }, }; } ) .on( DELETE_ORIGIN_ADDRESS, ( state, { payload: { deletedId } }: DeleteOriginAddressAction ) => { return { ...state, origin: { ...state.origin, addresses: state.origin.addresses.filter( ( originAddress ) => originAddress.id !== deletedId ), }, }; } ) .on( FETCH_ORIGIN_ADDRESSES, ( state, { payload: { addresses, isMainOriginInSyncWithStore: inSync, formattedStoreAddress, storeAddressDraft, }, }: FetchOriginAddressesAction ) => ( { ...state, origin: { ...state.origin, addresses, }, ...( inSync !== undefined && { isMainOriginInSyncWithStore: inSync, } ), ...( formattedStoreAddress !== undefined && { formattedStoreAddress, } ), ...( storeAddressDraft !== undefined && { storeAddressDraft, } ), } ) ) .on( SET_STORE_ADDRESS_SYNC_STATUS, ( state, { payload: { isMainOriginInSyncWithStore: inSync, formattedStoreAddress, storeAddressDraft, }, }: SetStoreAddressSyncStatusAction ) => ( { ...state, isMainOriginInSyncWithStore: inSync, formattedStoreAddress, storeAddressDraft, } ) ) .on( SET_DESTINATION_RESIDENTIAL, ( state, { payload: { residential } }: SetDestinationResidentialAction ) => { if ( ! state.destination ) { return state; } return { ...state, destination: { ...state.destination, address: { ...state.destination.address, residential, }, }, }; } ) .on( STATE_RESET, () => ( { ...getDefaultState(), } ) ) .bind< AddressActions >(); return addressReducer; };