import React, { useEffect, useRef, useState } from 'react'; import { View, Pressable, StyleSheet, Linking, Platform, TouchableOpacity, Modal } 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 CheckBox from '@react-native-community/checkbox'; import { PhoneInputNumber } from '../PhoneInputNumber'; import { FacebookLogin } from '../FacebookLogin'; import Recaptcha from 'react-native-recaptcha-that-works' import ReCaptcha from '@fatnlazycat/react-native-recaptcha-v3' import ReactNativeHapticFeedback from "react-native-haptic-feedback"; import { SignupForm as SignUpController, useLanguage, useConfig, useSession, ToastType, useToast, } from 'ordering-components/native'; import { useTheme } from 'styled-components/native'; import { FormSide, FormInput, SocialButtons } from './styles'; import { Otp } from '../LoginForm/Otp' import { ButtonsWrapper, LoginWith as SignupWith, TabBtn, OTab, OTabs, RecaptchaButton } from '../LoginForm/styles'; import NavBar from '../NavBar'; import Alert from '../../../../../src/providers/AlertProvider' import { OText, OButton, OInput } from '../shared'; import { SignupParams } from '../../types'; import { sortInputFields } from '../../utils'; import { GoogleLogin } from '../GoogleLogin'; 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, handleChangePromotions, enableReCaptcha, handleReCaptcha, generateOtpCode, numOtpInputs, setWillVerifyOtpState, handleChangeInput, willVerifyOtpState, setOtpState, setSignUpTab, signUpTab, useSignUpFullDetails, useSignUpOtpEmail, useSignUpOtpCellphone, useSignUpOtpWhatsapp, isGuest, setCellphoneStartZero } = props; const theme = useTheme(); const style = StyleSheet.create({ btnOutline: { backgroundColor: '#FFF', color: theme.colors.primary, }, inputStyle: { marginBottom: 20, borderWidth: 1, borderRadius: 7.6, }, wrappText: { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', marginBottom: 30, }, line: { height: 1, backgroundColor: theme.colors.border, flexGrow: 1, marginBottom: 7, }, checkBoxStyle: { width: 25, height: 25, } }); const [, { showToast }] = useToast(); const [, t] = useLanguage(); const [, { login }] = useSession(); const [{ configs }] = useConfig(); const { control, handleSubmit, clearErrors, errors, register, unregister, setValue } = useForm(); const [passwordSee, setPasswordSee] = useState(false); const [formValues, setFormValues] = useState(null); const [isModalVisible, setIsModalVisible] = useState(false); const [isLoadingVerifyModal, setIsLoadingVerifyModal] = useState(false); const [isFBLoading, setIsFBLoading] = useState(false); const [phoneInputData, setPhoneInputData] = useState({ error: '', phone: { country_phone_code: null, cellphone: null, country_code: null }, }); const [alertState, setAlertState] = useState({ open: false, title: '', content: [] }) const [recaptchaConfig, setRecaptchaConfig] = useState({}) const [recaptchaVerified, setRecaptchaVerified] = useState(false) const [tabLayouts, setTabLayouts] = useState({}) const [isCheckingCode, setCheckingCode] = useState(false) const [otpError, setOtpError] = useState(null) const tabsRef = useRef(null) 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 otpChannelRef = useRef(2); const showInputPhoneNumber = (validationFields?.fields?.checkout?.cellphone?.enabled ?? false) || configs?.verification_phone_required?.value === '1' const googleLoginEnabled = configs?.google_login_enabled?.value === '1' const facebookLoginEnabled = configs?.facebook_login_enabled?.value === '1' const appleLoginEnabled = Platform.OS === 'ios' && configs?.apple_login_enabled?.value === '1' const closeAlert = () => { setAlertState({ open: false, title: '', content: [] }) } const vibrateApp = (impact?: string) => { const options = { enableVibrateFallback: true, ignoreAndroidSystemSettings: false }; ReactNativeHapticFeedback.trigger(impact || "impactLight", options); } 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 handleOnLayout = (event: any, opc: string) => { const _tabLayouts = { ...tabLayouts } const categoryKey = opc _tabLayouts[categoryKey] = event.nativeEvent.layout setTabLayouts(_tabLayouts) } 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) => { login({ user, token: user.session.access_token, }); navigation.navigate('Home'); }; const handleSignUpTab = (tab: string) => { setSignUpTab && setSignUpTab(tab) clearErrors() } const onSubmit = (values?: any) => { if (phoneInputData.error && signUpTab !== 'otpEmail') { showToast(ToastType.Error, phoneInputData.error); vibrateApp() return; } if ( !phoneInputData.phone.country_phone_code && !phoneInputData.phone.cellphone && ((validationFields?.fields?.checkout?.cellphone?.enabled && validationFields?.fields?.checkout?.cellphone?.required) || configs?.verification_phone_required?.value === '1') && signUpTab !== 'otpEmail' ) { showToast( ToastType.Error, t( 'VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required.', ), ); vibrateApp() return; } if (signUpTab === 'otpEmail' || signUpTab === 'otpCellphone') { generateOtpCode && generateOtpCode({ ...values, ...((phoneInputData.phone.cellphone !== null && phoneInputData.phone.country_phone_code !== null) && { ...phoneInputData.phone }), country_code: phoneInputData.phone.country_code }, otpChannelRef.current) return } handleButtonSignupClick && handleButtonSignupClick({ ...values, ...((phoneInputData.phone.cellphone !== null && phoneInputData.phone.country_phone_code !== null) && { ...phoneInputData.phone }), country_code: phoneInputData.phone.country_code }); if (!formState.loading && formState.result.result && !formState.result.error) { handleSuccessSignup && handleSuccessSignup(formState.result.result); } }; const handleSingUpOtp = (value: string) => { setOtpState && setOtpState(value) } 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().trim().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'))) vibrateApp() } } const handleOpenRecaptcha = () => { setRecaptchaVerified(false) if (!recaptchaConfig?.siteKey) { showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key')); vibrateApp() return } if (!recaptchaConfig?.baseUrl) { showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url')); vibrateApp() return } recaptchaRef.current.open() } const onRecaptchaVerify = (token: any) => { setRecaptchaVerified(true) handleReCaptcha && handleReCaptcha({ code: token, version: recaptchaConfig?.version }) } const handleChangePhoneNumber = (number: any, rawNumber: any) => { setPhoneInputData({ ...phoneInputData, ...number, phone: { ...phoneInputData.phone, ...number.phone, country_code: phoneInputData.phone.country_code } }) setCellphoneStartZero && setCellphoneStartZero(rawNumber?.number && rawNumber?.countryCallingCode ? rawNumber?.number : null) } useEffect(() => { if (configs && Object.keys(configs).length > 0 && enableReCaptcha) { if (configs?.security_recaptcha_type?.value === 'v3' && configs?.security_recaptcha_score_v3?.value > 0 && configs?.security_recaptcha_site_key_v3?.value ) { setRecaptchaConfig({ version: 'v3', siteKey: configs?.security_recaptcha_site_key_v3?.value || null, baseUrl: configs?.security_recaptcha_base_url?.value || null }) return } if (configs?.security_recaptcha_site_key?.value) { setRecaptchaConfig({ version: 'v2', 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) { if (formState.result?.result?.[0] === 'ERROR_AUTH_VERIFICATION_CODE') { setRecaptchaVerified(false) setRecaptchaConfig({ version: 'v2', siteKey: configs?.security_recaptcha_site_key?.value || null, baseUrl: configs?.security_recaptcha_base_url?.value || null }) showToast(ToastType.Info, t('TRY_AGAIN', 'Please try again')) vibrateApp() return } formState.result?.result && formState.result?.result[0]?.includes("_") ? showToast(ToastType.Error, t(`${formState.result?.result[0]}`, 'Phone number already used')) : showToast(ToastType.Error, formState.result?.result[0]) formState.result?.result && vibrateApp() setIsLoadingVerifyModal(false); } }, [formState]); useEffect(() => { if (Object.keys(errors).length > 0) { setIsLoadingVerifyModal(false); vibrateApp() } }, [errors]) useEffect(() => { if (signUpTab === 'default' || signUpTab === 'otpCellphone') { register('cellphone', { required: isRequiredField('cellphone') ? t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone')) : null }) } else { unregister('cellphone') } }, [signUpTab]) useEffect(() => { if (phoneInputData?.phone?.cellphone) setValue('cellphone', phoneInputData?.phone?.cellphone, '') else setValue('cellphone', '') }, [phoneInputData?.phone?.cellphone]) 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); verifyPhoneState.result?.result && vibrateApp() setIsLoadingVerifyModal(false); return; } const okResult = verifyPhoneState.result?.result === 'OK'; if (okResult) { !isModalVisible && setIsModalVisible(true); setIsLoadingVerifyModal(false); } } }, [verifyPhoneState]); useEffect(() => { setPhoneInputData({ ...phoneInputData, phone: { ...phoneInputData.phone, country_code: configs?.default_country_code?.value } }) }, [configs]) useEffect(() => { if (checkPhoneCodeState?.result?.error) { const titleText = ( typeof checkPhoneCodeState?.result?.result === 'string' ? checkPhoneCodeState?.result?.result : checkPhoneCodeState?.result?.result[0].toString() ) || t('ERROR', 'Error') setCheckingCode(false) setOtpError(titleText) checkPhoneCodeState?.generate && setAlertState({ open: true, title: titleText, content: [] }) } }, [checkPhoneCodeState]) return ( {isGuest ? ( {t('SIGNUP', 'Signup')} ) : ( navigation?.canGoBack() && navigation.goBack()} showCall={false} btnStyle={{ paddingLeft: 0 }} titleWrapStyle={{ paddingHorizontal: 0 }} titleStyle={{ marginLeft: 0, marginRight: 0 }} /> )} {((Number(useSignUpFullDetails) + Number(useSignUpOtpEmail) + Number(useSignUpOtpCellphone)) > 1) && ( {useSignUpFullDetails && ( handleSignUpTab('default')} onLayout={(event: any) => handleOnLayout(event, 'default')} > {t('DEFAULT', 'Default')} )} {useSignUpOtpEmail && ( handleSignUpTab('otpEmail')} onLayout={(event: any) => handleOnLayout(event, 'otpEmail')} > {t('BY_OTP_EMAIL', 'by Otp Email')} )} {useSignUpOtpCellphone && ( handleSignUpTab('otpCellphone')} onLayout={(event: any) => handleOnLayout(event, 'otpCellphone')} > {t('BY_OTP_CELLPHONE', 'by Otp Cellphone')} )} )} {!(useChekoutFileds && validationFields?.loading) ? ( <> {sortInputFields({ values: validationFields?.fields?.checkout, }).map( (item: any, i: number) => { const field = item?.validation_field || item return (!notValidationFields.includes(field.code) && showField && showField(field.code) && (signUpTab === 'default' || (signUpTab === 'otpEmail' && field.code === 'email')) && ( {errors?.[`${field.code}`] && ( {errors?.[`${field.code}`]?.message} {errors?.[`${field.code}`]?.type === 'required' && '*'} )} ( 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)) } borderColor={errors?.[`${field.code}`] ? theme.colors.danger5 : theme.colors.border} /> )} name={field.code} rules={getInputRules(field)} defaultValue="" /> )) } )} {(!!showInputPhoneNumber && (signUpTab === 'default' || signUpTab === 'otpCellphone')) && ( setPhoneInputData({ ...phoneInputData, phone: { ...phoneInputData.phone, country_code: val.cca2 } })} textInputProps={{ returnKeyType: 'next', onSubmitEditing: () => passwordRef?.current?.focus?.(), }} isStartValidation={errors?.cellphone} /> )} {(enableReCaptcha && recaptchaConfig?.version) && ( <> {recaptchaConfig?.version === 'v3' ? ( ) : ( <> {recaptchaVerified ? ( ) : ( )} {t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')} setRecaptchaVerified(false)} /> )} )} {(signUpTab === 'default') && ( ( { onChange(newValue) handleChangePromotions() }} 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='promotions' defaultValue={false} /> {t('RECEIVE_NEWS_EXCLUSIVE_PROMOTIONS', 'Receive newsletters and exclusive promotions')} )} {configs?.terms_and_conditions?.value === 'true' && ( <> {errors?.termsAccept && ( {errors?.termsAccept?.message}* )} ( { 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 agree with')} handleOpenTermsUrl(configs?.terms_and_conditions_url?.value)} /> )} {signUpTab === 'default' && ( <> {errors?.password && ( {errors?.password?.message} {errors?.password?.type === 'required' && '*'} )} ( setPasswordSee(!passwordSee)} /> ) : ( setPasswordSee(!passwordSee)} /> ) } autoCapitalize='none' value={value} onChange={(val: any) => onChange(val)} returnKeyType="done" onSubmitEditing={handleSubmit(onSubmit)} blurOnSubmit forwardRef={passwordRef} borderColor={errors?.password ? theme.colors.danger5 : theme.colors.border} /> )} 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="" /> )} ) : ( )} {(signUpTab === 'otpCellphone' && useSignUpOtpWhatsapp) ? ( { otpChannelRef.current = 2; handleSubmit(onSubmit)() }} text={t('SEND_BY_SMS', 'Send by SMS')} imgRightSrc={null} isLoading={isLoadingVerifyModal} indicatorColor={theme.colors.white} parentStyle={{ flex: 1 }} style={{ borderRadius: 7.6 }} /> { otpChannelRef.current = 4; handleSubmit(onSubmit)() }} text={t('SEND_BY_WHATSAPP', 'Send by WhatsApp')} imgRightSrc={null} isLoading={isLoadingVerifyModal} indicatorColor={theme.colors.white} parentStyle={{ flex: 1 }} style={{ borderRadius: 7.6 }} /> ) : (signUpTab === 'otpEmail' || signUpTab === 'otpCellphone') ? ( ) : ( )} { onNavigationRedirect && loginButtonText && ( {t('MOBILE_FRONT_ALREADY_HAVE_AN_ACCOUNT', 'Already have an account?')} onNavigationRedirect('Login')}> {loginButtonText} ) } {configs && Object.keys(configs).length > 0 && !isGuest && ( (((configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') && configs?.facebook_id?.value && facebookLoginEnabled) || ((configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && googleLoginEnabled) || ((configs?.apple_login_client_id?.value !== '' && configs?.apple_login_client_id?.value !== null) && appleLoginEnabled)) && ( <> {t('OR', 'or')} {(configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') && configs?.facebook_id?.value && facebookLoginEnabled && ( { showToast(ToastType.Error, err), vibrateApp() }} handleLoading={(val: boolean) => setIsFBLoading(val)} handleSuccessFacebookLogin={handleSuccessFacebook} /> )} {(configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && googleLoginEnabled && ( { showToast(ToastType.Error, err), vibrateApp() }} handleLoading={(val: boolean) => setIsFBLoading(val)} handleSuccessGoogleLogin={handleSuccessFacebook} /> )} {(configs?.apple_login_client_id?.value !== '' && configs?.apple_login_client_id?.value !== null) && appleLoginEnabled && ( { showToast(ToastType.Error, err), vibrateApp() }} handleLoading={(val: boolean) => setIsFBLoading(val)} handleSuccessAppleLogin={handleSuccessFacebook} /> )} ) )} setWillVerifyOtpState && setWillVerifyOtpState(false)} animationType='slide' > setWillVerifyOtpState && setWillVerifyOtpState(false)} handleLoginOtp={handleSingUpOtp} onSubmit={onSubmit} setAlertState={setAlertState} /> ); }; export const SignupForm = (props: any) => { const _numOtpInputs = 6 const signupProps = { ...props, numOtpInputs: _numOtpInputs, isRecaptchaEnable: true, UIComponent: SignupFormUI, }; return ; };