import React, { useEffect, useState, useRef } from 'react'; import { StyleSheet, View, TouchableOpacity, Keyboard, TouchableWithoutFeedback, Platform, } from 'react-native'; import { AddressForm as AddressFormController, useLanguage, useConfig, useSession, useOrder, useToast, ToastType } from 'ordering-components/native'; import { DeviceOrientationMethods } from '../../../../../src/hooks/DeviceOrientation' import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete'; import Spinner from 'react-native-loading-spinner-overlay'; import { useForm, Controller } from 'react-hook-form'; import Geocoder from 'react-native-geocoding'; import { OInput, OButton, OModal, OIcon } from '../shared'; import { AddressFormParams } from '../../types'; import { getTraduction } from '../../utils'; import { useTheme } from 'styled-components/native'; import { GoogleMap } from '../GoogleMap'; import NavBar from '../NavBar'; import { AddressFormContainer, AutocompleteInput, IconsContainer, GoogleMapContainer, FormInput, } from './styles'; import { GPSButton } from '../GPSButton'; import { ScrollView } from 'react-native-gesture-handler'; const { useDeviceOrientation } = DeviceOrientationMethods const inputNames = [ { name: 'address', code: 'Address' }, { name: 'internal_number', code: 'Internal number' }, { name: 'zipcode', code: 'Zipcode' }, { name: 'address_notes', code: 'Address notes' }, ]; const AddressFormUI = (props: AddressFormParams) => { const { navigation, updateChanges, address, formState, isEditing, handleChangeInput, addressState, addressesList, saveAddress, isGuestUser, isRequiredField, showField, isFromProductsList, hasAddressDefault, afterSignup, businessSlug, isFromCheckout, onNavigationRedirect } = props; const theme = useTheme(); const [orientationState] = useDeviceOrientation(); const [autoCompleteInputFocused, setAutoCompleteInputFocused] = useState(false) const isEmptyField = (key: any, str: any) => isRequiredField && isRequiredField(key) && (!str || str.trim().length === 0) const tagsName = [ { icon: theme.images.general.tag_home, value: 'home' }, { icon: theme.images.general.tag_building, value: 'office' }, { icon: theme.images.general.tag_heart, value: 'favorite' }, { icon: theme.images.general.tag_plus, value: 'other' }, ]; const HEIGHT_SCREEN = orientationState?.dimensions?.height const styles = StyleSheet.create({ iconContainer: { display: 'flex', justifyContent: 'center', alignItems: 'center', paddingHorizontal: 18, paddingVertical: 5, backgroundColor: theme.colors.clear }, inputsStyle: { borderRadius: 10, marginBottom: 20, height: 50, maxHeight: 50, minHeight: 50, flex: 1, }, textAreaStyles: { borderRadius: 10, marginBottom: 20, height: 104, maxHeight: 104, minHeight: 104, textAlignVertical: 'top', alignItems: 'flex-start', }, pinIcon: { position: 'absolute', end: 0, top: 12, zIndex: 1002, right: autoCompleteInputFocused && ( !!address?.address || !!formState.changes?.address || !!addressState.address.address ) ? 30 : 15, width: 16 }, wrapperNavbar: Platform.OS === 'ios' ? { paddingVertical: 0, paddingLeft: 20, paddingRight: 20 } : { paddingVertical: 10, paddingLeft: 20, paddingRight: 20 } }); const [, t] = useLanguage(); const [{ auth }] = useSession(); const [, { showToast }] = useToast(); const [configState] = useConfig(); const [orderState] = useOrder(); const { handleSubmit, errors, control, setValue } = useForm(); const [autoCompleteAddress, setAutoCompleteAddress] = useState(false) const [toggleMap, setToggleMap] = useState(false); const [alertState, setAlertState] = useState<{ open: boolean; content: Array; key?: string | null; }>({ open: false, content: [], key: null }); const [addressTag, setAddressTag] = useState(addressState?.address?.tag); const [firstLocationNoEdit, setFirstLocationNoEdit] = useState({ value: { lat: null, lng: null }, address: null, }); const [isFirstTime, setIsFirstTime] = useState(true); const [locationChange, setLocationChange] = useState( isEditing ? addressState?.address?.location : formState.changes?.location ?? null, ); const [saveMapLocation, setSaveMapLocation] = useState(false); const [isKeyboardShow, setIsKeyboardShow] = useState(false); const [isSignUpEffect, setIsSignUpEffect] = useState(false); const [hasEditing, setAddressEditing] = useState(false); const googleInput: any = useRef(null); const internalNumberRef: any = useRef(null); const zipCodeRef: any = useRef(null); const addressNotesRef: any = useRef(null); const googleMapsApiKey = configState?.configs?.google_maps_api_key?.value; const isLocationRequired = configState.configs?.google_autocomplete_selection_required?.value === '1' || configState.configs?.google_autocomplete_selection_required?.value === 'true'; const maxLimitLocation = configState?.configs?.meters_to_change_address?.value; const countryCode = configState?.configs?.country_autocomplete?.value const isHideMap = theme?.address?.components?.map?.hidden const isHideIcons = theme?.address?.components?.icons?.hidden const continueAsGuest = () => navigation.navigate(!!businessSlug ? 'Business' : 'BusinessList', { isGuestUser: true }); const unaddressedTypes = configState?.configs?.unaddressed_order_types_allowed?.value.split('|').map((value: any) => Number(value)) || [] const isAllowUnaddressOrderType = unaddressedTypes.includes(orderState?.options?.type) const goToBack = () => navigation?.canGoBack() && navigation.goBack(); const getAddressFormatted = (address: any) => { const data: any = { address: null, error: null }; Geocoder.from(address) .then((json) => { if (json.results && json.results?.length > 0) { let postalCode = null; for (const component of json.results?.[0].address_components) { const addressType = component.types?.[0]; if (addressType === 'postal_code') { postalCode = component.short_name; break; } } data.address = { address, location: json.results[0].geometry.location, map_data: { library: 'google', place_id: json.results?.[0].place_id, }, }; if (postalCode) { data.address.zipcode = postalCode; } const arrayList = isEditing ? addressesList?.addresses?.filter( (address: any) => address.id !== addressState?.address?.id, ) || [] : addressesList || []; const addressToCompare = isEditing ? { ...addressState.address, ...data.address, ...formState.changes } : { ...data.address, ...formState?.changes }; const isAddressAlreadyExist = arrayList .map((address: any) => checkAddress(address, addressToCompare)) .some((value: any) => value) ?? false; if (!isAddressAlreadyExist) { saveAddress(data.address); if (isGuestUser) { continueAsGuest(); return; } if (!isGuestUser && !auth) { !isFromProductsList ? navigation.navigate('Business') : navigation?.canGoBack() && navigation.goBack(); } return; } setAlertState({ open: true, content: [t('ADDRESS_ALREADY_EXIST', 'The address already exists')], }); } else { setAlertState({ open: true, content: [t('ERROR_NOT_FOUND_ADDRESS', 'Error, address not found')], }); } }) .catch((error) => { setAlertState({ open: true, content: [error?.message || error?.toString()], }); }); }; const onSubmit = () => { if ( !auth && formState?.changes?.address === '' && addressState?.address?.address ) { setAlertState({ open: true, content: [ t( 'VALIDATION_ERROR_ADDRESS_REQUIRED', 'The field Address is required', ), ], }); return; } if (formState?.changes?.address && !formState?.changes?.location) { if (isLocationRequired) { setAlertState({ open: true, content: [ t( 'ADDRESS_REQUIRE_LOCATION', 'The address needs a location, please select one of the suggested', ), ], }); return; } getAddressFormatted(formState?.changes?.address); return; } const arrayList = isEditing ? addressesList?.addresses?.filter( (address: any) => address.id !== addressState?.address?.id, ) || [] : addressesList || []; const addressToCompare = isEditing ? { ...addressState.address, ...formState.changes } : formState?.changes; const isAddressAlreadyExist = arrayList .map((address: any) => checkAddress(address, addressToCompare)) .some((value: any) => value) ?? false; if (!isAddressAlreadyExist) { saveAddress(); if (isGuestUser) { continueAsGuest(); } if (!isGuestUser && !auth && !afterSignup) { !isFromProductsList ? navigation.navigate('Business') : navigation?.canGoBack() && navigation.goBack(); } return; } setAlertState({ open: true, content: [t('ADDRESS_ALREADY_EXIST', 'The address already exists')], }); }; /** * Returns true when the user made no changes * @param {object} address */ const checkAddress = (address: any, addressToCompare: any) => { const props = [ 'address', 'address_notes', 'zipcode', 'location', 'internal_number', ]; const values: any = []; props.forEach((prop) => { if (addressToCompare[prop]) { if (prop === 'location') { values.push( address[prop]?.lat === addressToCompare[prop]?.lat && address[prop]?.lng === addressToCompare[prop]?.lng, ); } else { values.push(address[prop] === addressToCompare[prop]); } } else { values.push(!address[prop]); } }); return values.every((value: any) => value); }; const handleChangeAddress = (data: any, details: any) => { const addressObj: any = {} if (details?.address_components) { details.address_components.map((component: any) => { const addressType = component.types[0] if (addressType === 'postal_code') { addressObj.zipcode = component.short_name } if (addressType === 'street_number') { addressObj.street_number = component.long_name } if (addressType === 'neighborhood') { addressObj.neighborhood = component.long_name } if (addressType === 'route') { addressObj.route = component.short_name } if (addressType === 'locality') { addressObj.locality = component.long_name } if (component.types?.includes('sublocality')) { addressObj.sublocality = component.long_name } if (addressType === 'country') { addressObj.country = component.long_name addressObj.country_code = component.short_name } if (addressType === 'administrative_area_level_1') { addressObj.state = component.long_name } if (addressType === 'administrative_area_level_2') { addressObj.city = component.long_name } }) } const addressSelected = { address: data?.description || data?.address, location: details?.geometry?.location, utc_offset: details?.utc_offset || null, map_data: { library: 'google', place_id: data.place_id }, zipcode: data?.zipcode || null, ...addressObj }; updateChanges(addressSelected); const zipcodeValue = addressObj.zipcode || addressSelected.zipcode; if (zipcodeValue) { setValue('zipcode', zipcodeValue); } if (addressSelected.address) { setValue('address', addressSelected.address); } }; const handleAddressTag = (tag: string) => { setAddressTag(tag); handleChangeInput({ target: { name: 'tag', value: tag, }, }); }; const handleToggleMap = () => { setToggleMap(!toggleMap); }; const showFieldWithTheme = (name) => { return !theme?.address?.components?.[name]?.hidden } useEffect(() => { if ( orderState.loading && !addressesList && orderState?.options?.address && auth && !afterSignup ) { isFromCheckout ? navigation.goBack() : !isFromProductsList ? navigation.navigate('BottomTab') : navigation.navigate('Business'); } }, [orderState?.options?.address]); useEffect(() => { if (alertState.open && alertState?.key !== 'ERROR_MAX_LIMIT_LOCATION') { alertState.content && showToast(ToastType.Error, alertState.content); } }, [alertState.content]); useEffect(() => { if (!auth) { inputNames.forEach((field) => setValue( field.name, formState?.changes[field.name] || (orderState?.options?.address && orderState?.options?.address[field.name]) || '', ), ); return; } if (!formState.loading && formState.result?.error) { setAlertState({ open: true, content: formState.result?.result || [t('ERROR', 'Error')], }); } setValue( 'address', formState?.changes?.address ?? addressState.address?.address ?? '', ); setValue( 'zipcode', formState?.changes?.zipcode ?? addressState.address?.zipcode ?? '', ); setValue( 'internal_number', formState?.changes?.internal_number ?? addressState.address?.internal_number ?? '', ); setValue( 'address_notes', formState?.changes?.address_notes ?? addressState.address?.address_notes ?? '', ); if (!isEditing) { formState?.changes?.address && setLocationChange(formState?.changes?.location); if ( formState?.changes?.address && formState?.changes?.address !== firstLocationNoEdit?.address && formState?.changes?.location && formState?.changes?.location?.lat !== firstLocationNoEdit.value?.lat && formState?.changes?.location?.lng !== firstLocationNoEdit.value?.lng ) { setFirstLocationNoEdit({ value: formState?.changes?.location, address: formState?.changes?.address, }); } } if (isEditing) { if (formState?.changes?.location) { const prevLocation = { lat: locationChange?.lat?.toFixed(5), lng: locationChange?.lng?.toFixed(5), }; const newLocation = { lat: formState?.changes?.location?.lat?.toFixed(5), lng: formState?.changes?.location?.lng?.toFixed(5), }; if ( prevLocation?.lat !== newLocation?.lat && prevLocation?.lng !== newLocation?.lng ) { setLocationChange(formState?.changes?.location); } } } }, [formState]); useEffect(() => { if (formState?.result && !formState?.loading) { if (formState?.error) { showToast(ToastType.Error, formState.error[0]); } else { showToast( ToastType.Success, t('UPDATE_SUCCESSFULLY', 'Update successfully'), ); } } }, [formState.result]); useEffect(() => { if (Object.keys(errors).length > 0) { // Convert all errors in one string to show in toast provider const list = Object.values(errors); let stringError = ''; list.map((item: any, i: number) => { stringError += i + 1 === list.length ? `- ${item.message}` : `- ${item.message}\n`; }); showToast(ToastType.Error, stringError); } }, [errors]); useEffect(() => { if (googleInput?.current) { googleInput?.current?.setAddressText( address?.address || formState.changes?.address || addressState.address.address || '', ); } }, []); useEffect(() => { Geocoder.init(googleMapsApiKey); }, [googleMapsApiKey]); useEffect(() => { const keyboardDidShowListener = Keyboard.addListener( 'keyboardDidShow', () => { setIsKeyboardShow(true); }, ); const keyboardDidHideListener = Keyboard.addListener( 'keyboardDidHide', () => { setIsKeyboardShow(false); }, ); return () => { keyboardDidShowListener.remove(); keyboardDidHideListener.remove(); }; }, []); useEffect(() => { if (!orderState.loading && auth && !hasAddressDefault && isSignUpEffect) { navigation.navigate('BottomTab'); } setIsSignUpEffect(true); }, [orderState.loading]); useEffect(() => { if (isAllowUnaddressOrderType) { onNavigationRedirect && onNavigationRedirect(!!businessSlug ? 'Business' : 'BusinessList') } }, [isAllowUnaddressOrderType]) return ( ( { handleChangeAddress(data, details); setAutoCompleteAddress(true); }} query={{ key: googleMapsApiKey, components: countryCode && countryCode !== '*' ? `country:${countryCode}` : '' }} fetchDetails ref={googleInput} textInputProps={{ onChangeText: (text) => { if (!isFirstTime) { handleChangeInput({ target: { name: 'address', value: text }, }); setValue('address', text); setAutoCompleteAddress(true) } setIsFirstTime(false); setAddressEditing(text.length == 0); }, onSubmitEditing: () => internalNumberRef?.current?.focus?.(), autoCorrect: false, blurOnSubmit: false, returnKeyType: 'next', onFocus: () => setAutoCompleteInputFocused(true), onBlur: () => setAutoCompleteInputFocused(false) }} onFail={(error) => setAlertState({ open: true, content: getTraduction(error, t), }) } styles={{ listView: { position: 'relative', borderBottomStartRadius: 15, borderBottomEndRadius: 15, elevation: 2, borderWidth: 1, borderColor: '#ddd', bottom: 20, }, container: { zIndex: 100, }, textInput: { borderWidth: 1, borderRadius: 7.6, borderColor: autoCompleteInputFocused ? theme.colors.primary : theme.colors.border, flexGrow: 1, fontSize: 15, paddingLeft: 16, paddingRight: 32, minHeight: 50, fontFamily: 'Poppins-Regular', marginBottom: 24 }, }} /> )} /> {( ((!isEditing && !isGuestUser) || (isEditing && !isGuestUser)) || (isGuestUser)) && ( { handleChangeAddress(data, detail); setValue('address', data.address); if (googleInput?.current) { googleInput?.current?.setAddressText(data.address); } }} IconButton={} isIntGeoCoder /> )} {!isHideMap && (locationChange || formState.changes?.location) && ( )} {showField?.('internal_number') && ((isRequiredField && isRequiredField('internal_number')) || showFieldWithTheme('internal_number')) && ( ( { handleChangeInput(data); setValue('internal_number', isEmptyField('internal_number', data?.target?.value) ? null : data); }} value={ address?.internal_number || formState.changes?.internal_number || addressState?.address?.internal_number || '' } isFocusHighlight style={{ ...styles.inputsStyle, marginRight: showField?.('internal_number') && showField?.('zipcode') ? 24 : 0 }} forwardRef={internalNumberRef} returnKeyType="next" onSubmitEditing={() => zipCodeRef?.current?.focus?.()} blurOnSubmit={false} /> )} /> )} {showField?.('zipcode') && ((isRequiredField && isRequiredField('zipcode')) || showFieldWithTheme('zipcode')) && ( ( { handleChangeInput(data); setValue('zipcode', isEmptyField('zipcode', data?.target?.value) ? null : data); }} value={ address?.zipcode || formState.changes?.zipcode || addressState.address.zipcode || '' } isFocusHighlight style={styles.inputsStyle} forwardRef={zipCodeRef} returnKeyType="next" onSubmitEditing={() => addressNotesRef?.current?.focus?.()} blurOnSubmit={false} /> )} /> )} {showField?.('address_notes') && ((isRequiredField && isRequiredField('address_notes')) || showFieldWithTheme('address_notes')) && ( ( { handleChangeInput(text); setValue('address_notes', text); }} value={ address?.address_notes || formState.changes?.address_notes || addressState.address.address_notes || '' } multiline isFocusHighlight style={styles.textAreaStyles} returnKeyType="done" forwardRef={addressNotesRef} blurOnSubmit /> )} /> )} {!isHideIcons && ( {tagsName.map((tag) => ( handleAddressTag(tag.value)}> ))} )} {Object.keys(formState?.changes).length > 0 ? ( ) : ( navigation?.canGoBack() && navigation.goBack()} /> )} handleToggleMap()} entireModal customClose> {(locationChange || formState.changes?.location) && ( )} { setSaveMapLocation(true) handleToggleMap && handleToggleMap() }} /> ); }; export const AddressForm = (props: AddressFormParams) => { const addressFormProps = { ...props, UIComponent: AddressFormUI, }; return ; };