import React, { createRef, useEffect, useRef, useState } from 'react'; import { View, Pressable, StyleSheet, Keyboard, Linking, Platform, TouchableOpacity, ScrollView } from 'react-native'; import { useForm, Controller } from 'react-hook-form'; import Spinner from 'react-native-loading-spinner-overlay'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import Recaptcha from 'react-native-recaptcha-that-works' import { PhoneInputNumber } from '../PhoneInputNumber' import { FacebookLogin } from '../FacebookLogin' import { GoogleLogin } from '../GoogleLogin' import { SignupForm as SignUpController, useLanguage, useConfig, useSession, ToastType, useToast } from 'ordering-components/native'; import { FormSide, FormInput, ButtonsSection, SocialButtons } from './styles' import { LoginWith as SignupWith, OTab, OTabs, RecaptchaButton } from '../LoginForm/styles' import { _removeStoreData } from '../../providers/StoreUtil'; import NavBar from '../NavBar' import { VerifyPhone } from '../VerifyPhone'; import { OText, OButton, OInput, OModal } from '../shared'; import CheckBox from '@react-native-community/checkbox'; import { SignupParams } from '../../types'; import { sortInputFields } from '../../utils'; import { useTheme } from 'styled-components/native'; import { AppleLogin } from '../AppleLogin'; const notValidationFields = ['coupon', 'driver_tip', 'mobile_phone', 'address', 'address_notes'] const SignupFormUI = (props: SignupParams) => { const { navigation, loginButtonText, signupButtonText, onNavigationRedirect, formState, validationFields, showField, isRequiredField, useChekoutFileds, useSignupByEmail, useSignupByCellphone, handleSuccessSignup, handleButtonSignupClick, verifyPhoneState, checkPhoneCodeState, setCheckPhoneCodeState, handleSendVerifyCode, handleCheckPhoneCode, notificationState, enableReCaptcha, handleReCaptcha } = props const theme = useTheme() const style = StyleSheet.create({ btnOutline: { backgroundColor: '#FFF', color: theme.colors.primary }, inputStyle: { marginBottom: 25, borderWidth: 1, borderColor: theme.colors.disabled }, wrappText: { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', marginBottom: 30 }, checkBoxStyle: { width: 25, height: 25, } }); const showInputPhoneNumber = validationFields?.fields?.checkout?.cellphone?.enabled ?? false const [, { showToast }] = useToast(); const [, t] = useLanguage(); const [, { login }] = useSession(); const [{ configs }] = useConfig(); const { control, handleSubmit, errors } = useForm(); const [passwordSee, setPasswordSee] = useState(false); const [formValues, setFormValues] = useState(null) const [isModalVisible, setIsModalVisible] = useState(false); const [isLoadingVerifyModal, setIsLoadingVerifyModal] = useState(false); const [signupTab, setSignupTab] = useState(useSignupByCellphone && !useSignupByEmail ? 'cellphone' : 'email') const [isLoadingSocialButton, setIsLoadingSocialButton] = useState(false); const [phoneInputData, setPhoneInputData] = useState({ error: '', phone: { country_phone_code: null, cellphone: null } }); const [recaptchaConfig, setRecaptchaConfig] = useState({}) const [recaptchaVerified, setRecaptchaVerified] = useState(false) const nameRef = useRef(null) const lastnameRef = useRef(null) const middleNameRef = useRef(null) const secondLastnameRef = useRef(null) const emailRef = useRef(null) const phoneRef = useRef(null) const passwordRef = useRef(null) const recaptchaRef = useRef({}); const googleLoginEnabled = configs?.google_login_enabled?.value === '1' || !configs?.google_login_enabled?.enabled const anySocialButtonActivated = ((configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') && configs?.facebook_id?.value) || (configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) || (configs?.apple_login_client_id?.value !== '' && configs?.apple_login_client_id?.value !== null) const handleRefs = (ref: any, code: string) => { switch (code) { case 'name': { nameRef.current = ref break } case 'middle_name': { middleNameRef.current = ref } case 'lastname': { lastnameRef.current = ref break } case 'second_lastname': { secondLastnameRef.current = ref break } case 'email': { emailRef.current = ref break } } } const handleFocusRef = (code: string) => { switch (code) { case 'name': { nameRef?.current?.focus() break } case 'middle_name': { middleNameRef?.current?.focus() break } case 'lastname': { lastnameRef?.current?.focus() break } case 'second_lastname': { secondLastnameRef?.current?.focus() break } case 'email': { emailRef?.current?.focus() break } } } const getNextFieldCode = (index: number) => { const fields = sortInputFields({ values: validationFields?.fields?.checkout })?.filter((field: any) => !notValidationFields.includes(field.code) && showField(field.code)) return fields[index + 1]?.code } const handleSuccessFacebook = (user: any) => { _removeStoreData('isGuestUser') login({ user, token: user.session.access_token }) } const handleSuccessApple = (user: any) => { _removeStoreData('isGuestUser') login({ user, token: user?.session?.access_token }) } const handleChangeTab = (val: string) => { setSignupTab(val); setPasswordSee(false); } const handleVerifyCodeClick = (values: any) => { const formData = values || formValues handleSendVerifyCode && handleSendVerifyCode({ ...formData, ...phoneInputData.phone }) setIsLoadingVerifyModal(true) } // get object with rules for hook form inputs const getInputRules = (field: any) => { const rules: any = { required: isRequiredField(field.code) ? t(`VALIDATION_ERROR_${field.code.toUpperCase()}_REQUIRED`, `${field.name} is required`) .replace('_attribute_', t(field.name, field.code)) : null } if (field.code && field.code === 'email') { rules.pattern = { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: t('INVALID_ERROR_EMAIL', 'Invalid email address').replace('_attribute_', t('EMAIL', 'Email')) } } return rules } const handleChangeInputEmail = (value: string, onChange: any) => { onChange(value.toLowerCase().replace(/[&,()%";:ç?<>{}\\[\]\s]/g, '')) } const handleOpenTermsUrl = async (url: any) => { const supported = await Linking.canOpenURL(url); if (supported) { await Linking.openURL(url); } else { showToast(ToastType.Error, t('VALIDATION_ERROR_ACTIVE_URL', 'The _attribute_ is not a valid URL.').replace('_attribute_', t('URL', 'URL'))) } } const handleOpenRecaptcha = () => { setRecaptchaVerified(false) if (recaptchaVerified) { handleReCaptcha && handleReCaptcha('') return } if (!recaptchaConfig?.siteKey) { showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key')); return } if (!recaptchaConfig?.baseUrl) { showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url')); return } recaptchaRef.current.open() } const onRecaptchaVerify = (token: any) => { setRecaptchaVerified(true) handleReCaptcha && handleReCaptcha(token) } const onSubmit = (values: any) => { Keyboard.dismiss() if (phoneInputData.error) { showToast(ToastType.Error, phoneInputData.error); return } if ( !phoneInputData.phone.country_phone_code && !phoneInputData.phone.cellphone && validationFields?.fields?.checkout?.cellphone?.enabled && validationFields?.fields?.checkout?.cellphone?.required ) { showToast(ToastType.Error, t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required.')) return } if (signupTab === 'email' || !useSignupByCellphone) { handleButtonSignupClick && handleButtonSignupClick({ ...values, ...phoneInputData.phone }) if (!formState.loading && formState.result.result && !formState.result.error) { handleSuccessSignup && handleSuccessSignup(formState.result.result) } return } setFormValues(values) handleVerifyCodeClick(values) } useEffect(() => { if (configs && Object.keys(configs).length > 0 && enableReCaptcha) { setRecaptchaConfig({ siteKey: configs?.security_recaptcha_site_key?.value || null, baseUrl: configs?.security_recaptcha_base_url?.value || null }) } }, [configs, enableReCaptcha]) useEffect(() => { if (!formState.loading && formState.result?.error) { formState.result?.result && showToast( ToastType.Error, formState.result?.result[0] ) setIsLoadingVerifyModal(false) } }, [formState]) useEffect(() => { if (Object.keys(errors).length > 0) { // Convert all errors in one string to show in toast provider const list = Object.values(errors) if (phoneInputData.error) { list.push({ message: phoneInputData.error }) } if ( !phoneInputData.error && !phoneInputData.phone.country_phone_code && !phoneInputData.phone.cellphone && validationFields?.fields?.checkout?.cellphone?.enabled && validationFields?.fields?.checkout?.cellphone?.required ) { list.push({ message: t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required.') }) } let stringError = '' list.map((item: any, i: number) => { stringError += (i + 1) === list.length ? `- ${item.message}` : `- ${item.message}\n` }) showToast(ToastType.Error, stringError) setIsLoadingVerifyModal(false) } }, [errors]) useEffect(() => { if (verifyPhoneState && !verifyPhoneState?.loading) { if (verifyPhoneState.result?.error) { const message = typeof verifyPhoneState?.result?.result === 'string' ? verifyPhoneState?.result?.result : verifyPhoneState?.result?.result[0] verifyPhoneState.result?.result && showToast( ToastType.Error, message ) setIsLoadingVerifyModal(false) return } const okResult = verifyPhoneState.result?.result === 'OK' if (okResult) { !isModalVisible && setIsModalVisible(true) setIsLoadingVerifyModal(false) } } }, [verifyPhoneState]) return ( navigation?.canGoBack() && navigation.goBack()} showCall={false} btnStyle={{ paddingLeft: 0 }} /> {useSignupByEmail && useSignupByCellphone && configs && Object.keys(configs).length > 0 && (configs?.twilio_service_enabled?.value === 'true' || configs?.twilio_service_enabled?.value === '1') && ( {useSignupByEmail && ( handleChangeTab('email')}> {t('SIGNUP_BY_EMAIL', 'Signup by Email')} )} {useSignupByCellphone && ( handleChangeTab('cellphone')}> {t('SIGNUP_BY_PHONE', 'Signup by Phone')} )} )} {!(useChekoutFileds && validationFields?.loading && validationFields?.fields?.checkout) ? ( <> {sortInputFields({ values: validationFields?.fields?.checkout }).map((field: any, i: number) => !notValidationFields.includes(field.code) && ( showField && showField(field.code) && ( ( field.code !== 'email' ? onChange(val) : handleChangeInputEmail(val, onChange)} autoCapitalize={field.code === 'email' ? 'none' : 'sentences'} autoCorrect={field.code === 'email' && false} type={field.code === 'email' ? 'email-address' : 'default'} autoCompleteType={field.code === 'email' ? 'email' : 'off'} returnKeyType='next' blurOnSubmit={false} forwardRef={(ref: any) => handleRefs(ref, field.code)} onSubmitEditing={() => field.code === 'email' ? phoneRef?.current?.focus?.() : handleFocusRef(getNextFieldCode(i))} /> )} name={field.code} rules={getInputRules(field)} defaultValue="" /> ) )) } {!!showInputPhoneNumber && ( setPhoneInputData(val)} forwardRef={phoneRef} textInputProps={{ returnKeyType: 'next', onSubmitEditing: () => passwordRef?.current?.focus?.() }} /> )} {signupTab !== 'cellphone' && ( ( setPasswordSee(!passwordSee)} /> : setPasswordSee(!passwordSee)} /> } value={value} onChange={(val: any) => onChange(val)} returnKeyType='done' onSubmitEditing={handleSubmit(onSubmit)} blurOnSubmit forwardRef={passwordRef} /> )} name="password" rules={{ required: isRequiredField('password') ? t('VALIDATION_ERROR_PASSWORD_REQUIRED', 'The field Password is required') .replace('_attribute_', t('PASSWORD', 'password')) : null, minLength: { value: 8, message: t('VALIDATION_ERROR_PASSWORD_MIN_STRING', 'The Password must be at least 8 characters.') .replace('_attribute_', t('PASSWORD', 'Password')).replace('_min_', 8) } }} defaultValue="" /> )} ) : ( )} {configs?.terms_and_conditions?.value === 'true' && ( ( { onChange(newValue) }} boxType={'square'} tintColors={{ true: theme.colors.primary, false: theme.colors.disabled }} tintColor={theme.colors.disabled} onCheckColor={theme.colors.primary} onTintColor={theme.colors.primary} style={Platform.OS === 'ios' && style.checkBoxStyle} /> )} name='termsAccept' rules={{ required: t('VALIDATION_ERROR_ACCEPTED', 'The _attribute_ must be accepted.').replace('_attribute_', t('TERMS_AND_CONDITIONS', 'Terms & Conditions')) }} defaultValue={false} /> {t('TERMS_AND_CONDITIONS_TEXT', 'I’m agree with')} handleOpenTermsUrl(configs?.terms_and_conditions_url?.value)}> {t('TERMS_AND_CONDITIONS', 'Terms & Conditions')} )} {enableReCaptcha && ( <> {recaptchaVerified ? ( ) : ( )} {t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')} setRecaptchaVerified(false)} footerComponent={ recaptchaRef.current.close()} style={{ borderRadius: 0 }} text={t('CLOSE', 'Close')} bgColor={theme.colors.primary} borderColor={theme.colors.primary} textStyle={{ color: 'white' }} imgRightSrc={null} />} /> )} {signupTab === 'cellphone' && useSignupByEmail && useSignupByCellphone ? ( ) : ( )} { onNavigationRedirect && loginButtonText && ( {t('MOBILE_FRONT_ALREADY_HAVE_AN_ACCOUNT', 'Already have an account?')} onNavigationRedirect('Login')}> {loginButtonText} ) } {configs && Object.keys(configs).length > 0 && anySocialButtonActivated && ( {t('SELECT_AN_OPTION_TO_LOGIN', 'Select an option to login')} {(configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') && configs?.facebook_id?.value && ( showToast(ToastType.Error, err)} handleLoading={(val: boolean) => setIsLoadingSocialButton(val)} handleSuccessFacebookLogin={handleSuccessFacebook} /> )} {(configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && googleLoginEnabled && ( showToast(ToastType.Error, err)} handleLoading={(val: boolean) => setIsLoadingSocialButton(val)} handleSuccessGoogleLogin={handleSuccessFacebook} /> )} {(configs?.apple_login_client_id?.value !== '' && configs?.apple_login_client_id?.value !== null) && ( showToast(ToastType.Error, err)} handleLoading={(val: boolean) => setIsLoadingSocialButton(val)} handleSuccessApple={handleSuccessApple} /> )} )} setIsModalVisible(false)} > ); }; export const SignupForm = (props: any) => { const signupProps = { ...props, isRecaptchaEnable: true, UIComponent: SignupFormUI, }; return ; };