@shopgate/engage
Version:
Shopgate's ENGAGE library.
204 lines (199 loc) • 6.46 kB
JavaScript
import "core-js/modules/es.array.reduce.js";
import React, { useMemo, useCallback, useRef } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { css } from 'glamor';
import { i18n, useRoute, historyPop, getNumberOfAddressLines } from '@shopgate/engage/core';
import { convertValidationErrors, useFormState } from '@shopgate/engage/core/hooks/useFormState';
import { responsiveMediaQuery } from '@shopgate/engage/styles';
import { getShopSettings } from '@shopgate/engage/core/config';
import { getPreferredLocationAddress } from '@shopgate/engage/locations/selectors';
import { FormBuilder, RippleButton } from '@shopgate/engage/components';
import { StylePresets } from '@shopgate/engage/components/Form';
import { LoadingProvider } from '@shopgate/pwa-common/providers';
import { useAddressBook } from '@shopgate/engage/checkout';
import { generateFormConfig } from "./ProfileContact.config";
import { generateConstraints } from "./ProfileContact.constraints";
import { addCustomerContacts } from "../../actions/addContacts";
import { getCustomer } from "../../selectors/customer";
import { updateCustomerContact } from "../../actions/updateContact";
/**
* @param {Object} state State
* @returns {Object}
*/
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const mapStateToProps = state => ({
shopSettings: getShopSettings(state),
userLocation: getPreferredLocationAddress(state),
customer: getCustomer(state),
numberOfAddressLines: getNumberOfAddressLines(state)
});
/**
* @param {Object} dispatch Dispatch
* @returns {Object}
*/
const mapDispatchToProps = dispatch => ({
pop: props => dispatch(historyPop(props)),
addContact: contact => dispatch(addCustomerContacts({
contacts: [contact]
})),
updateContact: (contactId, contact) => dispatch(updateCustomerContact({
contactId,
contact
}))
});
const styles = {
root: css({
margin: 16
}),
form: css({
...StylePresets.OUTLINED_FORM_FIELDS,
...StylePresets.TWO_COLUMN_LAYOUT,
' .profileAddressFormRegion': {
[responsiveMediaQuery('>=md', {
webOnly: false
})]: {
marginRight: '50%'
}
}
}).toString(),
button: css({
'&&': {
marginTop: 8,
marginRight: 16,
backgroundColor: 'var(--color-primary)',
borderRadius: 5,
fontSize: 14,
textTransform: 'none',
padding: 0,
[responsiveMediaQuery('<md', {
webOnly: false
})]: {
width: '100%',
marginRight: 0
}
}
}).toString(),
ripple: css({
padding: '8px 16px'
}).toString(),
actions: css({
display: 'flex',
justifyContent: 'flex-end',
flexDirection: 'row',
[responsiveMediaQuery('<md', {
webOnly: false
})]: {
flex: 1
}
}).toString()
};
/**
* @param {Object} props Props.
* @returns {JSX}
*/
const ProfileContact = ({
shopSettings,
userLocation,
addContact,
updateContact,
pop,
customer,
numberOfAddressLines
}) => {
const {
updateOrderWithContact,
type,
isCheckout
} = useAddressBook();
const formContainerRef = useRef(null);
const {
pathname,
state: {
contact = {}
}
} = useRoute();
const addressLines = useMemo(() => {
/**
* Determine the number of address fields with a value. Customers are supposed to edit their
* old addresses after the numberOfAddressLines shop setting was changed.
*/
const addressLinesInContact = Object.keys(contact).reduce((acc, key) => {
if (key.startsWith('address') && contact[key]) {
return parseInt(key.charAt(key.length - 1), 10);
}
return acc;
}, 0);
return Math.max(numberOfAddressLines, addressLinesInContact);
}, [contact, numberOfAddressLines]);
const formConfig = useMemo(() => generateFormConfig({
supportedCountries: shopSettings?.supportedCountries,
countrySortOrder: shopSettings?.countrySortOrder,
userLocation,
isCheckout,
type,
numberOfAddressLines: addressLines
}), [addressLines, isCheckout, shopSettings, type, userLocation]);
const constraints = useMemo(() => generateConstraints(isCheckout), [isCheckout]);
const handleSubmit = useCallback(async values => {
LoadingProvider.setLoading(pathname);
let contactId = contact.id;
const finalValues = {
...values,
...(isCheckout && !contact?.emailAddress ? {
emailAddress: customer?.emailAddress
} : {})
};
const fieldsUpdated = Object.keys(values).filter(key => !['isDefaultBilling', 'isDefaultShipping'].includes(key)).some(key => values[key] !== contact[key]);
try {
if (contact?.id) {
await updateContact(contact.id, finalValues);
} else {
({
contactIds: [contactId]
} = await addContact(finalValues));
}
if (updateOrderWithContact && fieldsUpdated) {
await updateOrderWithContact(contactId);
return;
}
pop();
} catch (error) {
// Generic error handling is used.
}
LoadingProvider.unsetLoading(pathname);
}, [addContact, contact, customer, isCheckout, pathname, pop, updateContact, updateOrderWithContact]);
const formState = useFormState(contact, handleSubmit, constraints, formContainerRef);
const validationErrors = useMemo(() => convertValidationErrors(formState.validationErrors || {}), [formState.validationErrors]);
/* eslint-disable react-hooks/exhaustive-deps */
const handleUpdate = useCallback(values => {
formState.setValues(values);
}, [formState.setValues]);
/* eslint-enable react-hooks/exhaustive-deps */
return /*#__PURE__*/_jsxs("div", {
className: styles.root,
ref: formContainerRef,
children: [/*#__PURE__*/_jsx(FormBuilder, {
name: "ProfileAddressForm",
className: styles.form,
config: formConfig,
defaults: contact,
validationErrors: validationErrors,
handleUpdate: handleUpdate
}), /*#__PURE__*/_jsx("div", {
className: styles.actions,
children: /*#__PURE__*/_jsx(RippleButton, {
className: styles.button,
rippleClassName: styles.ripple,
type: "primary",
onClick: formState.handleSubmit,
children: i18n.text('account.profile.form.save')
})
})]
});
};
ProfileContact.defaultProps = {
customer: null,
numberOfAddressLines: null
};
export default connect(mapStateToProps, mapDispatchToProps)(ProfileContact);