diff --git a/manifest.json b/manifest.json index bf2d1bf2..71857321 100644 --- a/manifest.json +++ b/manifest.json @@ -1,13 +1,15 @@ { "name": "address-form", "vendor": "vtex", - "version": "3.36.2", + "version": "3.36.3-beta.7", "title": "address-form React component", "description": "address-form React component", "defaultLocale": "en", "mustUpdateAt": "2019-01-08", "categories": [], - "registries": ["smartcheckout"], + "registries": [ + "smartcheckout" + ], "settingsSchema": {}, "dependencies": { "vtex.checkout": "0.x", diff --git a/messages/context.json b/messages/context.json index bfeeb91f..faede78d 100644 --- a/messages/context.json +++ b/messages/context.json @@ -98,5 +98,6 @@ "address-form.field.municipalityDelegation": "Municipality/Delegation", "address-form.field.province": "Province", "address-form.field.receiverName": "Recipient", - "address-form.field.county": "County" + "address-form.field.county": "County", + "address-form.field.contactId": "Contact Id" } diff --git a/messages/en.json b/messages/en.json index bfeeb91f..faede78d 100644 --- a/messages/en.json +++ b/messages/en.json @@ -98,5 +98,6 @@ "address-form.field.municipalityDelegation": "Municipality/Delegation", "address-form.field.province": "Province", "address-form.field.receiverName": "Recipient", - "address-form.field.county": "County" + "address-form.field.county": "County", + "address-form.field.contactId": "Contact Id" } diff --git a/messages/es.json b/messages/es.json index 8f6e6444..054a4bc6 100644 --- a/messages/es.json +++ b/messages/es.json @@ -98,5 +98,6 @@ "address-form.field.municipalityDelegation": "Municipio/DelegaciĆ³n", "address-form.field.province": "Provincia", "address-form.field.receiverName": "Destinatario", - "address-form.field.county": "Condado" + "address-form.field.county": "Condado", + "address-form.field.contactId": "Id de Contacto" } diff --git a/react/AddressContainer.js b/react/AddressContainer.js index a0a6e2a5..d4c8a143 100644 --- a/react/AddressContainer.js +++ b/react/AddressContainer.js @@ -12,6 +12,19 @@ import { AddressContext } from './addressContainerContext' import { injectRules } from './addressRulesContext' class AddressContainer extends Component { + constructor(props) { + super(props) + this.contactInfo = { + id: null, + email: null, + firstName: null, + lastName: null, + document: null, + phone: null, + documentType: null, + } + } + componentDidMount() { if ( this.props.shouldHandleAddressChangeOnMount && @@ -106,12 +119,34 @@ class AddressContainer extends Component { onChangeAddress(validatedAddress, ...args) } + handleContactInfoChange = (field) => { + return (this.contactInfo = { + ...this.contactInfo, + ...field, + }) + } + render() { - const { children, Input, address } = this.props - const { handleAddressChange } = this + const { children, Input, address, handleCompleteOmnishipping } = this.props + const { handleAddressChange, handleContactInfoChange, contactInfo } = this + + if (!address?.contactId?.value) { + address.contactId = { value: null } + } else { + address.contactId = { value: address.contactId.value } + } return ( - + {children} ) @@ -136,6 +171,8 @@ AddressContainer.propTypes = { autoCompletePostalCode: PropTypes.bool, shouldHandleAddressChangeOnMount: PropTypes.bool, shouldAddFocusToNextInvalidField: PropTypes.bool, + contactInfo: PropTypes.object, + handleCompleteOmnishipping: PropTypes.func, } export default injectRules(AddressContainer) diff --git a/react/ContactInfoForm.js b/react/ContactInfoForm.js new file mode 100644 index 00000000..da655770 --- /dev/null +++ b/react/ContactInfoForm.js @@ -0,0 +1,236 @@ +import React, { useEffect, useState } from 'react' + +import styles from './ContactInfoForm.module.css' + +const Input = ({ + id, + label, + type = 'text', + value = '', + name = '', + onChange = (_) => {}, + placeholder = 'Optional', + error = '', +}) => ( +

+ + onChange(e)} + /> + {error ? This field is required. : null} +

+) + +let gguid = 1 + +function getGGUID(prefix = 0) { + return `${prefix}${(gguid++ * new Date().getTime() * -1) + .toString() + .replace('-', '')}` +} + +const ContactInfoForm = ({ + address, + onChangeAddress, + contactInfo = {}, + onChangeContactInfo = (_, __) => {}, + clientProfileData, + prevContactInfo, +}) => { + const isPrevUserData = areEqual(prevContactInfo, clientProfileData) + const [useUserInfo, setUseUserInfo] = useState( + prevContactInfo?.id + ? address?.contactId?.value === prevContactInfo?.id && isPrevUserData + : true + ) + + const [localUserInfo, setLocalUserInfo] = useState( + prevContactInfo && !isPrevUserData + ? prevContactInfo + : { + email: null, + firstName: '', + lastName: '', + document: null, + phone: '', + documentType: null, + } + ) + + useEffect(() => { + if (address?.contactId?.value) { + return + } + + address.contactId.value = prevContactInfo?.id ?? getGGUID(1234) + + onChangeContactInfo({ id: address.contactId.value }) + onChangeAddress(address) + }, [address, onChangeAddress, onChangeContactInfo, prevContactInfo]) + + useEffect(() => { + if (useUserInfo) { + onChangeContactInfo({ + id: address?.contactId?.value ?? '', + email: '', + firstName: clientProfileData?.firstName ?? '', + lastName: clientProfileData?.lastName ?? '', + document: '', + phone: clientProfileData?.phone ?? '', + documentType: '', + }) + } else { + onChangeContactInfo(localUserInfo) + } + + onChangeAddress(address) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + useUserInfo, + localUserInfo, + onChangeContactInfo, + clientProfileData, + onChangeAddress, + ]) + + const handleInputChange = (e) => { + const { name, value } = e.target + + setLocalUserInfo((prev) => ({ ...prev, [name]: value })) + } + + return ( +
+ + {!useUserInfo ? ( +
+

Receiver Information

+
+
+ + +
+ +
+
+ ) : null} +
+ ) +} + +const areEqual = (obj1, obj2) => { + if (obj1 === obj2) { + return true + } + + if (!obj1 || !obj2) { + return false + } + + for (const key in obj1) { + if (key !== 'id' && key !== 'error') { + if (obj1[key] && obj1[key] !== obj2[key]) { + return false + } + } + } + + return true +} + +export const isContactInfoFormValid = (contactInfo, onChangeContactInfo) => { + const { firstName, lastName, phone } = contactInfo + + if (firstName) { + if (lastName) { + if (phone) { + return true + } + + onChangeContactInfo({ + error: { phone: 'Required' }, + }) + + return false + } + + onChangeContactInfo({ + error: { lastName: 'Required' }, + }) + + return false + } + + onChangeContactInfo({ + error: { firstName: 'Required' }, + }) + + return false +} + +export const getPreviousContactInfo = (state) => { + const { orderForm: OFState, addressForm } = state + const browseOF = vtexjs?.checkout?.orderForm + + const orderForm = browseOF ?? OFState?.orderForm + + let contactId = + orderForm?.shippingData?.contactInformation?.find( + ({ id }) => + addressForm?.addresses?.[addressForm?.residentialId]?.contactId + ?.value === id + ) ?? null + + if (!contactId) { + contactId = + orderForm?.shippingData?.contactInformation?.find( + ({ id }) => + orderForm?.shippingData?.availableAddresses?.find( + ({ addressId }) => addressId === addressForm?.residentialId + )?.contactId === id + ) ?? null + } + + return contactId +} + +export default ContactInfoForm diff --git a/react/ContactInfoForm.module.css b/react/ContactInfoForm.module.css new file mode 100644 index 00000000..1eede0b5 --- /dev/null +++ b/react/ContactInfoForm.module.css @@ -0,0 +1,48 @@ +.mainContactInfoContainer { +} +.mainContactInfoForm { +} + +.contactInfoCheckbox input { + margin: 0; + margin-right: 6px; + width: 16px; + height: 16px; +} + +.contactInfoCheckbox { + display: flex; + align-items: center; + font-size: 12px; + margin-top: 16px; + margin-bottom: 12px; +} + +.contactInfoTitle { + font-size: 18px !important; + font-weight: 500 !important; +} + +.contactInfoFlex { + display: flex; + align-items: center; + gap: 12px; +} + +.contactInfoFlex .input { + width: 100%; +} + +.mainContactInfoForm :global(.input) { + padding-bottom: 15px; +} + +.mainContactInfoForm :global(.input):global(.error) { + padding-bottom: 0; +} + +@media (max-width: 768px) { + .contactInfoFlex { + flex-wrap: wrap; + } +} diff --git a/react/__mocks__/addressWithObjectValuesWithoutValidation.ts b/react/__mocks__/addressWithObjectValuesWithoutValidation.ts index 72b8fc3b..4c9932d9 100644 --- a/react/__mocks__/addressWithObjectValuesWithoutValidation.ts +++ b/react/__mocks__/addressWithObjectValuesWithoutValidation.ts @@ -16,4 +16,5 @@ export default { street: { value: null }, isDisposable: { value: true }, addressQuery: { value: null }, + contactId: { value: null }, } as AddressWithValidation diff --git a/react/__mocks__/newAddress.ts b/react/__mocks__/newAddress.ts index f2d8ed66..2c22dbfe 100644 --- a/react/__mocks__/newAddress.ts +++ b/react/__mocks__/newAddress.ts @@ -16,4 +16,5 @@ export default { street: { value: null }, addressQuery: { value: null }, isDisposable: { value: true }, + contactId: { value: null }, } as AddressWithValidation diff --git a/react/__mocks__/whitespaceAddress.ts b/react/__mocks__/whitespaceAddress.ts index f79964ed..f694193d 100644 --- a/react/__mocks__/whitespaceAddress.ts +++ b/react/__mocks__/whitespaceAddress.ts @@ -16,4 +16,5 @@ export default { street: { value: ' ' }, addressQuery: { value: null }, isDisposable: { value: true }, + contactId: { value: null }, } as AddressWithValidation diff --git a/react/addressContainerContext.tsx b/react/addressContainerContext.tsx index 6a858a7f..af81f307 100644 --- a/react/addressContainerContext.tsx +++ b/react/addressContainerContext.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useContext } from 'react' import type { AddressWithValidation } from './types/address' @@ -6,6 +6,9 @@ type Context = { address: AddressWithValidation handleAddressChange: (address: AddressWithValidation) => void Input: React.ComponentType + contactInfo?: any + handleContactInfoChange?: any + handleCompleteOmnishipping?: any } export const AddressContext = React.createContext( @@ -27,7 +30,10 @@ export function injectAddressContext( )} diff --git a/react/components.ts b/react/components.ts index 4d277c36..e76b4e4a 100644 --- a/react/components.ts +++ b/react/components.ts @@ -6,6 +6,7 @@ import AddressContainer from './AddressContainer' import AutoCompletedFields from './AutoCompletedFields' import AddressRules from './AddressRules' import AddressSubmitter from './AddressSubmitter' +import ContactInfoForm from './ContactInfoForm' import PostalCodeLoader from './postalCodeFrom/PostalCodeLoader' import GoogleMapsContainer from './geolocation/GoogleMapsContainer' import Map from './geolocation/Map' @@ -18,6 +19,7 @@ export default { AddressRules, AddressSubmitter, AddressSummary, + ContactInfoForm, AutoCompletedFields, CountrySelector, GoogleMapsContainer, diff --git a/react/country/IRL.ts b/react/country/IRL.ts index 61528128..45b390eb 100644 --- a/react/country/IRL.ts +++ b/react/country/IRL.ts @@ -74,6 +74,7 @@ const rules: PostalCodeRules = { size: 'large', }, { + hidden: true, name: 'receiverName', elementName: 'receiver', maxLength: 750, @@ -81,6 +82,14 @@ const rules: PostalCodeRules = { size: 'xlarge', required: true, }, + { + hidden: true, + name: 'contactId', + maxLength: 100, + label: 'contactId', + size: 'xlarge', + required: true, + }, ], geolocation: { postalCode: { diff --git a/react/helpers.ts b/react/helpers.ts index 9523adfb..a119124a 100644 --- a/react/helpers.ts +++ b/react/helpers.ts @@ -7,8 +7,14 @@ import { validateAddress, } from './validateAddress' import getAddressByGeolocation from './geolocation/Utils' +import { + getPreviousContactInfo, + isContactInfoFormValid, +} from './ContactInfoForm' export default { + getPreviousContactInfo, + isContactInfoFormValid, isValidAddress, validateField, validateAddress, diff --git a/react/index.ts b/react/index.ts index 1181cf4e..897c41c5 100644 --- a/react/index.ts +++ b/react/index.ts @@ -7,6 +7,7 @@ export { default as AddressContainer } from './AddressContainer' export { default as AutoCompletedFields } from './AutoCompletedFields' export { default as AddressRules } from './AddressRules' export { default as AddressSubmitter } from './AddressSubmitter' +export { default as ContactInfoForm } from './ContactInfoForm' export { default as components } from './components' diff --git a/react/types/address.ts b/react/types/address.ts index 21792e74..310b7176 100644 --- a/react/types/address.ts +++ b/react/types/address.ts @@ -33,6 +33,7 @@ export interface Address { geoCoordinates?: number[] | null receiverName?: string | null addressQuery?: string | null + contactId?: string | null } export type AddressValues = Address[Fields] | null diff --git a/react/types/css.d.ts b/react/types/css.d.ts new file mode 100644 index 00000000..4a332752 --- /dev/null +++ b/react/types/css.d.ts @@ -0,0 +1,5 @@ +declare module '*.css' { + const styles: Record + + export default styles +} diff --git a/react/validateAddress.ts b/react/validateAddress.ts index 7ebeaa24..b6d47c88 100644 --- a/react/validateAddress.ts +++ b/react/validateAddress.ts @@ -182,6 +182,8 @@ export function validateField( case 'addressQuery': case 'isDisposable': + + case 'contactId': return defaultValidation(value, name, address, rules) default: {