UNPKG

@shopgate/engage

Version:
353 lines (338 loc) • 14.4 kB
import React, { useMemo, useState, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; import pickBy from 'lodash/pickBy'; import identity from 'lodash/identity'; import appConfig from '@shopgate/pwa-common/helpers/config'; import { useFormState } from '@shopgate/engage/core/hooks/useFormState'; import { useScrollTo } from '@shopgate/engage/core/hooks/useScrollTo'; import { LoadingProvider, i18n, useAsyncMemo, useRoute, SHOP_SETTING_REGISTRATION_MODE_EXTENDED } from '@shopgate/engage/core'; import { ADDRESS_TYPE_SHIPPING, ADDRESS_TYPE_PICKUP } from '@shopgate/engage/checkout'; import { extractDefaultValues } from "../../account/helper/form"; import { GUEST_CHECKOUT_PATTERN, GUEST_CHECKOUT_PAYMENT_PATTERN } from "../../checkout/constants/routes"; import Context from "./GuestRegistrationProvider.context"; import { generateBillingConstraints, generateShippingConstraints, generatePickupConstraints, generateSelfPickupConstraints, generateExtraConstraints } from "./GuestRegistrationProvider.constraints"; import { MARKETING_OPT_IN_DEFAULT, ELEMENT_ID_SHIPPING_CONTACT, ELEMENT_ID_SHIPPING_CONTACT_TOGGLE, ELEMENT_ID_PICKUP_CONTACT } from "../constants"; import connect from "./GuestRegistrationProvider.connector"; import { jsx as _jsx } from "react/jsx-runtime"; const initialAddressFormState = { firstName: '', lastName: '', company: '', address1: '', address2: '', city: '', country: '', postalCode: '', mobile: '' }; const initialBillingFormState = { ...initialAddressFormState }; const initialShippingFormState = { ...initialAddressFormState }; const initialPickupFormState = { pickupPerson: 'me', firstName: '', lastName: '', mobile: '', emailAddress: '' }; const initialOptInFormState = { marketingOptIn: MARKETING_OPT_IN_DEFAULT }; /** * Converts validation errors into errors for form builder. * @param {Object} validationErrors The validation errors. * @returns {Array} */ const convertValidationErrors = validationErrors => Object.keys(validationErrors).map(key => ({ path: key, message: i18n.text(validationErrors[key]) })); /** * @param {Object} props The provider props * @returns {JSX} */ const GuestRegistrationProvider = ({ children, formContainerRef, isDataReady, userLocation, customerAttributes, billingAddress, shippingAddress, pickupAddress, customer, billingPickupEquals, billingShippingEquals, numberOfAddressLines, cartHasDirectShipItems, orderReserveOnly, orderNeedsPayment, isShippingAddressSelectionEnabled, isPickupContactSelectionEnabled, shopSettings, submitGuestRegistration, prepareCheckout, isLastStackEntry, historyPop, historyPush }) => { const { scrollTo } = useScrollTo(formContainerRef); const isShippingFormEnabled = useMemo(() => cartHasDirectShipItems, [cartHasDirectShipItems]); const getIsShippingFormVisible = useCallback(() => isShippingFormEnabled && !!shippingAddress && !billingShippingEquals, [billingShippingEquals, isShippingFormEnabled, shippingAddress]); const [isLocked, setIsLocked] = useState(false); const [isBillingFormSubmitted, setIsBillingFormSubmitted] = useState(false); const [isShippingFormSubmitted, setIsShippingFormSubmitted] = useState(false); const [isExtraFormSubmitted, setIsExtraFormSubmitted] = useState(false); const [isPickupFormSubmitted, setIsPickupFormSubmitted] = useState(false); const [billingFormRequestErrors, setBillingFormRequestErrors] = useState(null); const [shippingFormRequestErrors, setShippingFormRequestErrors] = useState(null); const [pickupFormRequestErrors, setPickupFormRequestErrors] = useState(null); const [extraFormRequestErrors, setExtraFormRequestErrors] = useState(null); const [isShippingFormVisible, setIsShippingFormVisible] = useState(getIsShippingFormVisible()); const [pickupConstraints, setPickupConstraints] = useState(generateSelfPickupConstraints()); const { query: { edit: guestRegistrationEditMode = null } } = useRoute(); // Initialize checkout process. const [isInitialized] = useAsyncMemo(async () => { if (guestRegistrationEditMode) { return true; } try { setIsLocked(true); await prepareCheckout({ initializePayment: false, initializeOrder: !!isLastStackEntry }); setIsLocked(false); return true; } catch (error) { setIsLocked(false); return false; } }, [], false); useEffect(() => { setIsShippingFormVisible(getIsShippingFormVisible()); }, [getIsShippingFormVisible, setIsShippingFormVisible]); // Determine values to prefill some form fields const userCountry = useMemo(() => userLocation?.country || appConfig?.marketId || null, [userLocation]); const userRegion = useMemo(() => userLocation?.region || null, [userLocation]); // ===== billing form ===== const defaultBillingFormState = useMemo(() => ({ ...initialBillingFormState, country: userCountry, region: userRegion, ...pickBy(billingAddress || {}, identity) }), [billingAddress, userCountry, userRegion]); const billingConstraints = useMemo(() => generateBillingConstraints(orderReserveOnly), [orderReserveOnly]); const handleBillingFormSubmit = useCallback(() => { setIsBillingFormSubmitted(true); }, [setIsBillingFormSubmitted]); const billingFormState = useFormState(defaultBillingFormState, handleBillingFormSubmit, billingConstraints, formContainerRef); // ===== shipping form ===== const defaultShippingFormState = useMemo(() => ({ ...initialShippingFormState, country: userCountry, region: userRegion, ...(!billingShippingEquals ? pickBy(shippingAddress || {}, identity) : {}) }), [billingShippingEquals, shippingAddress, userCountry, userRegion]); const shippingConstraints = useMemo(() => isShippingFormVisible ? generateShippingConstraints() : {}, [isShippingFormVisible]); const handleShippingFormSubmit = useCallback(() => { setIsShippingFormSubmitted(true); }, [setIsShippingFormSubmitted]); const shippingFormState = useFormState(defaultShippingFormState, handleShippingFormSubmit, shippingConstraints, formContainerRef); // ===== pickup contact form ===== const defaultPickupFormState = useMemo(() => ({ ...initialPickupFormState, ...(!billingPickupEquals ? pickBy(pickupAddress || {}, identity) : {}), pickupPerson: billingPickupEquals ? 'me' : 'someoneElse' }), [billingPickupEquals, pickupAddress]); const handlePickupFormSubmit = useCallback(() => { setIsPickupFormSubmitted(true); }, []); const pickupFormState = useFormState(defaultPickupFormState, handlePickupFormSubmit, pickupConstraints, formContainerRef); useEffect(() => { setPickupConstraints(pickupFormState.values.pickupPerson === 'me' ? generateSelfPickupConstraints() : generatePickupConstraints()); }, [pickupFormState.values.pickupPerson]); // ===== extra form ===== const defaultExtraFormState = useMemo(() => ({ ...initialOptInFormState, ...extractDefaultValues(customer?.attributes || []) }), [customer]); const extraConstraints = useMemo(() => generateExtraConstraints(customerAttributes), [customerAttributes]); const handleExtraFormSubmit = useCallback(() => { setIsExtraFormSubmitted(true); }, []); const extraFormState = useFormState(defaultExtraFormState, handleExtraFormSubmit, extraConstraints, formContainerRef); const handleSubmit = useCallback(() => { billingFormState.handleSubmit(); shippingFormState.handleSubmit(); pickupFormState.handleSubmit(); extraFormState.handleSubmit(); }, [billingFormState, extraFormState, pickupFormState, shippingFormState]); useEffect(() => { if (!isBillingFormSubmitted || !isShippingFormSubmitted || !isPickupFormSubmitted || !isExtraFormSubmitted) { return; } // Break the process when one of the forms has validation errors from the constraints if (!billingFormState.valid || !shippingFormState.valid || !pickupFormState.valid || !extraFormState.valid) { setIsBillingFormSubmitted(false); setIsShippingFormSubmitted(false); setIsPickupFormSubmitted(false); setIsExtraFormSubmitted(false); } /** Async wrapper for submit registration */ const fn = async () => { setIsLocked(true); let response; try { response = await submitGuestRegistration({ billingFormData: billingFormState.values, ...(isShippingFormVisible ? { shippingFormData: shippingFormState.values } : {}), ...(isPickupContactSelectionEnabled ? { pickupFormData: pickupFormState.values } : {}), extraFormData: extraFormState.values, processShipping: isShippingAddressSelectionEnabled }); } catch (e) { setIsLocked(false); return; } const { errors } = response || {}; if (!errors) { setIsLocked(false); LoadingProvider.setLoading(GUEST_CHECKOUT_PAYMENT_PATTERN); if (guestRegistrationEditMode) { historyPop(); } else { historyPush({ pathname: GUEST_CHECKOUT_PAYMENT_PATTERN }); } return; } // Updated the request validation errors setBillingFormRequestErrors(errors?.billingFormData || null); setShippingFormRequestErrors(errors?.shippingFormData || null); setPickupFormRequestErrors(errors?.pickupFormData || null); setExtraFormRequestErrors(errors?.extraFormData || null); // Release forms for additional submits setIsBillingFormSubmitted(false); setIsShippingFormSubmitted(false); setIsPickupFormSubmitted(false); setIsExtraFormSubmitted(false); setIsLocked(false); }; fn(); /* eslint-disable react-hooks/exhaustive-deps */ }, [isBillingFormSubmitted, isShippingFormSubmitted, isPickupFormSubmitted, isExtraFormSubmitted]); /* eslint-enable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */ useEffect(() => { billingFormState.scrollToError(); }, [billingFormRequestErrors, shippingFormRequestErrors, pickupFormRequestErrors, billingFormState.scrollToError]); /* eslint-enable react-hooks/exhaustive-deps */ useEffect(() => { if (isLocked) { LoadingProvider.setLoading(GUEST_CHECKOUT_PATTERN); return; } LoadingProvider.unsetLoading(GUEST_CHECKOUT_PATTERN); }, [isLocked]); useEffect(() => { const timer = setTimeout(() => { if (!guestRegistrationEditMode) { return; } let scrollToElement; if (guestRegistrationEditMode === ADDRESS_TYPE_SHIPPING) { if (isShippingFormVisible) { scrollToElement = ELEMENT_ID_SHIPPING_CONTACT; } else { scrollToElement = ELEMENT_ID_SHIPPING_CONTACT_TOGGLE; } } else if (guestRegistrationEditMode === ADDRESS_TYPE_PICKUP) { scrollToElement = ELEMENT_ID_PICKUP_CONTACT; } if (scrollToElement) { scrollTo(`#${scrollToElement}`); } }, 500); return () => clearTimeout(timer); /* eslint-disable react-hooks/exhaustive-deps */ }, []); /* eslint-enable react-hooks/exhaustive-deps */ const value = useMemo(() => ({ isShippingFormEnabled, isShippingFormVisible, setIsShippingFormVisible, orderReserveOnly, orderNeedsPayment, guestRegistrationEditMode, defaultBillingFormState, updateBillingForm: billingFormState.setValues, defaultShippingFormState, updateShippingForm: shippingFormState.setValues, defaultPickupFormState, updatePickupForm: pickupFormState.setValues, defaultExtraFormState, updateExtraForm: extraFormState.setValues, billingFormValidationErrors: convertValidationErrors(billingFormState.validationErrors || billingFormRequestErrors || {}), shippingFormValidationErrors: convertValidationErrors(shippingFormState.validationErrors || shippingFormRequestErrors || {}), pickupFormValidationErrors: convertValidationErrors(pickupFormState.validationErrors || pickupFormRequestErrors || {}), extraFormValidationErrors: convertValidationErrors(extraFormState.validationErrors || extraFormRequestErrors || {}), isShippingAddressSelectionEnabled, isPickupContactSelectionEnabled, customerAttributes, numberOfAddressLines, userLocation, supportedCountries: shopSettings.supportedCountries, countrySortOrder: shopSettings.countrySortOrder, isLocked, handleSubmit, /** * Handling of registrationMode "simple" is not implemented for checkout. But since * the guest registration is actually an order update and not a real registration, we * hardcode the registration mode here, since the provider value will be used inside * the shared form components. */ registrationMode: SHOP_SETTING_REGISTRATION_MODE_EXTENDED, isBillingAddressSelectionEnabled: true }), [isShippingFormEnabled, isShippingFormVisible, defaultBillingFormState, billingFormState.setValues, billingFormState.validationErrors, defaultShippingFormState, shippingFormState.setValues, shippingFormState.validationErrors, defaultExtraFormState, extraFormState.setValues, extraFormState.validationErrors, extraFormRequestErrors, defaultPickupFormState, pickupFormState.setValues, pickupFormState.validationErrors, billingFormRequestErrors, shippingFormRequestErrors, pickupFormRequestErrors, orderReserveOnly, orderNeedsPayment, guestRegistrationEditMode, isShippingAddressSelectionEnabled, isPickupContactSelectionEnabled, customerAttributes, numberOfAddressLines, userLocation, shopSettings.supportedCountries, shopSettings.countrySortOrder, isLocked, handleSubmit]); if (!isDataReady || !isInitialized) { return null; } return /*#__PURE__*/_jsx(Context.Provider, { value: value, children: children }); }; GuestRegistrationProvider.defaultProps = { children: null, customer: null, formContainerRef: null, billingAddress: null, shippingAddress: null, pickupAddress: null, billingPickupEquals: true, billingShippingEquals: true, cartHasDirectShipItems: false, orderReserveOnly: false, orderNeedsPayment: false, numberOfAddressLines: null, isShippingAddressSelectionEnabled: false, isPickupContactSelectionEnabled: false }; export default connect(GuestRegistrationProvider);