import React, { useEffect, useState, useRef } from 'react'; import { StyleSheet, View, Keyboard, Modal, Platform } from 'react-native'; import Spinner from 'react-native-loading-spinner-overlay'; import { useForm, Controller } from 'react-hook-form'; import { PhoneInputNumber } from '../PhoneInputNumber'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import Recaptcha from 'react-native-recaptcha-that-works' import ReCaptcha from '@fatnlazycat/react-native-recaptcha-v3' import ReactNativeHapticFeedback from "react-native-haptic-feedback"; import { LoginForm as LoginFormController, useLanguage, useConfig, useSession, ToastType, useToast, } from 'ordering-components/native'; import { useTheme } from 'styled-components/native'; import { FacebookLogin } from '../FacebookLogin'; import { VerifyPhone } from '../../../../../src/components/VerifyPhone'; import { OModal } from '../../../../../src/components/shared'; import { Container, ButtonsWrapper, LoginWith, FormSide, FormInput, OTabs, OTab, SocialButtons, OrSeparator, LineSeparator, SkeletonWrapper, TabBtn, RecaptchaButton } from './styles'; import NavBar from '../NavBar'; import { OText, OButton, OInput } from '../shared'; import { LoginParams } from '../../types'; import { Placeholder, PlaceholderLine, Fade } from 'rn-placeholder'; import { GoogleLogin } from '../GoogleLogin'; import { AppleLogin } from '../AppleLogin'; import { Otp } from './Otp' import { TouchableOpacity } from 'react-native-gesture-handler'; import Alert from '../../../../../src/providers/AlertProvider' const LoginFormUI = (props: LoginParams) => { const { loginTab, formState, navigation, useLoginByEmail, useLoginByCellphone, useLoginOtp, loginButtonText, forgotButtonText, verifyPhoneState, checkPhoneCodeState, registerButtonText, setCheckPhoneCodeState, handleButtonLoginClick, handleSendVerifyCode, handleCheckPhoneCode, onNavigationRedirect, notificationState, handleReCaptcha, enableReCaptcha, otpType, setOtpType, generateOtpCode, useLoginOtpEmail, useLoginOtpCellphone, useLoginOtpWhatsapp, isGuest, setCellphoneStartZero } = props; const [, { showToast }] = useToast(); const [, t] = useLanguage(); const [{ configs }] = useConfig(); const [, { login }] = useSession(); const { control, handleSubmit, errors, reset, register, setValue } = useForm(); const [passwordSee, setPasswordSee] = useState(false); const [isLoadingVerifyModal, setIsLoadingVerifyModal] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false); const [isFBLoading, setIsFBLoading] = useState(false); const [willVerifyOtpState, setWillVerifyOtpState] = useState(false) const [phoneInputData, setPhoneInputData] = useState({ error: '', phone: { country_phone_code: null, cellphone: null, }, }); const [recaptchaConfig, setRecaptchaConfig] = useState({}) const [recaptchaVerified, setRecaptchaVerified] = useState(false) const [alertState, setAlertState] = useState({ open: false, title: '', content: [] }) const [tabLayouts, setTabLayouts] = useState({}) const [otpError, setOtpError] = useState(null) const tabsRef = useRef(null) const enabledPoweredByOrdering = configs?.powered_by_ordering_module?.value const theme = useTheme(); const isOtpEmail = loginTab === 'otp' && otpType === 'email' const isOtpCellphone = loginTab === 'otp' && otpType === 'cellphone' const [isCheckingCode, setCheckingCode] = useState(false) 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 loginStyle = StyleSheet.create({ btnOutline: { backgroundColor: '#FFF', color: theme.colors.primary, borderRadius: 7.6, }, inputStyle: { marginBottom: 28, borderWidth: 1, // borderColor: theme.colors.border, borderRadius: 7.6, }, line: { height: 1, backgroundColor: theme.colors.border, flexGrow: 1, marginBottom: 7, }, recaptchaIcon: { width: 100, height: 100, }, borderStyleBase: { width: 30, height: 45 }, borderStyleHighLighted: { borderColor: "#03DAC6", }, underlineStyleBase: { width: 45, height: 60, borderWidth: 1, fontSize: 16 }, underlineStyleHighLighted: { borderColor: theme.colors.primary, color: theme.colors.primary, fontSize: 16 }, }); const emailRef = useRef({}); const passwordRef = useRef({}); const recaptchaRef = useRef({}); const otpChannelRef = useRef(2); const handleChangeTab = (val: string, otpType?: string) => { props.handleChangeTab(val); setPasswordSee(false); handleCategoryScroll(otpType ? `${val}_${otpType}` : val) }; const vibrateApp = (impact?: string) => { const options = { enableVibrateFallback: true, ignoreAndroidSystemSettings: false }; ReactNativeHapticFeedback.trigger(impact || "impactLight", options); } const onSubmit = (values?: any) => { Keyboard.dismiss(); if (loginTab === 'otp') { if (phoneInputData.error && (loginTab !== 'otp' || (otpType === 'cellphone' && loginTab === 'otp'))) { showToast(ToastType.Error, t('INVALID_PHONE_NUMBER', 'Invalid phone number')); vibrateApp() return } if (otpType === 'cellphone' && phoneInputData?.error && !phoneInputData?.phone?.cellphone) { showToast(ToastType.Error, t('PHONE_NUMBER_REQUIRED', 'Phone number is required')); return } if (loginTab === 'otp') { generateOtpCode({ ...values, ...phoneInputData.phone }, otpChannelRef.current) } setWillVerifyOtpState(true) } else { if (phoneInputData.error) { showToast(ToastType.Error, phoneInputData.error); vibrateApp() return; } handleButtonLoginClick({ ...values, ...phoneInputData.phone, }); } }; const handleVerifyCodeClick = () => { if (phoneInputData.error) { showToast(ToastType.Error, phoneInputData.error); vibrateApp() return; } if ( !phoneInputData.error && !phoneInputData.phone.country_phone_code && !phoneInputData.phone.cellphone ) { showToast( ToastType.Error, t( 'VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required.', ), ); vibrateApp() return; } handleSendVerifyCode && handleSendVerifyCode(phoneInputData.phone); setIsLoadingVerifyModal(true); }; const handleSuccessFacebook = (user: any) => { login({ user, token: user.session.access_token, }); }; const handleChangeInputEmail = (value: string, onChange: any) => { onChange(value.toLowerCase().trim().replace(/[&,()%";:รง?<>{}\\[\]\s]/g, '')); }; 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({ code: token, version: recaptchaConfig?.version }) } const handleChangeOtpType = (type: string) => { handleChangeTab('otp', type) setOtpType(type) } const handleLoginOtp = async (code: string) => { if (!code) return const logged = await handleButtonLoginClick({ code }) setCheckingCode(false) if (logged) { setWillVerifyOtpState(false) } else { setOtpError(t('OTP_CODE_INCORRECT', 'Otp code incorrect')) } } const closeAlert = () => { setAlertState({ open: false, title: '', content: [] }) } const handleCategoryScroll = (opc: string) => { tabsRef.current.scrollTo({ x: tabLayouts?.[opc]?.x - 40, animated: true }) } const handleOnLayout = (event: any, opc: string) => { const _tabLayouts = { ...tabLayouts } const categoryKey = opc _tabLayouts[categoryKey] = event.nativeEvent.layout setTabLayouts(_tabLayouts) } const handleChangePhoneNumber = (number: any, rawNumber: any) => { setPhoneInputData(number) 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 && showToast( ToastType.Error, typeof formState.result?.result === 'string' ? formState.result?.result : formState.result?.result[0], ); formState.result?.result && vibrateApp() } }, [formState]); 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(() => { if (phoneInputData?.phone?.cellphone) setValue('cellphone', phoneInputData?.phone?.cellphone, '') else setValue('cellphone', '') }, [phoneInputData?.phone?.cellphone]) useEffect(() => { register('cellphone', { required: loginTab === 'cellphone' ? t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone')) : null }) }, [register]) useEffect(() => { reset() }, [loginTab]) useEffect(() => { if (checkPhoneCodeState?.result?.error) { setAlertState({ open: true, content: t(checkPhoneCodeState?.result?.error, checkPhoneCodeState?.result?.error), title: '' }) } }, [checkPhoneCodeState]) useEffect(() => { if (!!Object.values(errors)?.length) vibrateApp() }, [errors]) return ( {isGuest ? ( {t('LOGIN', 'Login')} ) : ( navigation?.canGoBack() && navigation.goBack()} showCall={false} btnStyle={{ paddingLeft: 0 }} titleWrapStyle={{ paddingHorizontal: 0 }} titleStyle={{ marginRight: 0, marginLeft: 0 }} /> )} {(Number(useLoginByEmail) + Number(useLoginByCellphone) + Number(useLoginOtpEmail) + Number(useLoginOtpCellphone) > 1) && ( {useLoginByEmail && ( handleChangeTab('email')} onLayout={(event: any) => handleOnLayout(event, 'email')} > {t('LOGIN_BY_EMAIL', 'by Email')} )} {useLoginByCellphone && ( handleChangeTab('cellphone')} onLayout={(event: any) => handleOnLayout(event, 'cellphone')} > {t('LOGIN_BY_PHONE', 'by Phone')} )} {useLoginOtpEmail && ( handleChangeOtpType('email')} onLayout={(event: any) => handleOnLayout(event, 'otp_email')} > {t('BY_OTP_EMAIL', 'By Otp Email')} )} {useLoginOtpCellphone && ( handleChangeOtpType('cellphone')} onLayout={(event: any) => handleOnLayout(event, 'otp_cellphone')} > {t('BY_OTP_PHONE', 'By Otp Phone')} )} )} {(useLoginByCellphone || useLoginByEmail || useLoginOtp) && ( {((useLoginByEmail && loginTab === 'email') || (loginTab === 'otp' && otpType === 'email')) && ( <> {errors?.email && ( {errors?.email?.message}{errors?.email?.type === 'required' && '*'} )} ( { handleChangeInputEmail(e, onChange); }} value={value} autoCapitalize="none" autoCorrect={false} type="email-address" autoCompleteType="email" returnKeyType="next" onSubmitEditing={() => passwordRef.current?.focus()} blurOnSubmit={false} forwardRef={emailRef} borderColor={errors?.email ? theme.colors.danger5 : theme.colors.border} /> )} name="email" rules={{ required: { value: true, message: t( 'VALIDATION_ERROR_EMAIL_REQUIRED', 'The field Email is required', ).replace('_attribute_', t('EMAIL', 'Email')) }, 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')), } }} defaultValue="" /> )} {((useLoginByCellphone && loginTab === 'cellphone') || (loginTab === 'otp' && otpType === 'cellphone')) && ( passwordRef?.current?.focus?.(), }} isStartValidation={errors?.cellphone} /> )} {errors?.password && ( {errors?.password?.message}{errors?.password?.type === 'required' && '*'} )} {loginTab !== 'otp' && ( ( setPasswordSee(!passwordSee)} color={theme.colors.disabled} /> ) : ( setPasswordSee(!passwordSee)} color={theme.colors.disabled} /> ) } autoCapitalize='none' value={value} forwardRef={passwordRef} onChange={(val: any) => onChange(val)} returnKeyType="done" onSubmitEditing={handleSubmit(onSubmit)} blurOnSubmit borderColor={errors?.password ? theme.colors.danger5 : theme.colors.border} /> )} name="password" rules={{ required: { value: true, message: t( 'VALIDATION_ERROR_PASSWORD_REQUIRED', 'The field Password is required', ).replace('_attribute_', t('PASSWORD', 'Password')) } }} defaultValue="" /> )} {onNavigationRedirect && forgotButtonText && loginTab !== 'otp' && ( onNavigationRedirect('Forgot')}> {forgotButtonText} )} {(enableReCaptcha && recaptchaConfig?.version) && ( <> {recaptchaConfig?.version === 'v3' ? ( ) : ( <> {recaptchaVerified ? ( ) : ( )} {t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')} setRecaptchaVerified(false)} /> ) } )} {(loginTab === 'otp' && otpType === 'cellphone' && useLoginOtpWhatsapp) ? ( { otpChannelRef.current = 2; handleSubmit(onSubmit)() }} text={t('SEND_BY_SMS', 'Send by SMS')} imgRightSrc={null} isLoading={formState.loading} parentStyle={{ flex: 1 }} style={{ borderRadius: 7.6 }} /> { otpChannelRef.current = 4; handleSubmit(onSubmit)() }} text={t('SEND_BY_WHATSAPP', 'Send by WhatsApp')} imgRightSrc={null} isLoading={formState.loading} parentStyle={{ flex: 1 }} style={{ borderRadius: 7.6 }} /> ) : ( )} {onNavigationRedirect && registerButtonText && ( {t('NEW_ON_PLATFORM', 'New on Ordering?')} onNavigationRedirect('Signup')}> {t('CREATE_ACCOUNT', 'Create account')} )} )} {useLoginByCellphone && loginTab === 'cellphone' && configs && Object.keys(configs).length > 0 && (configs?.twilio_service_enabled?.value === 'true' || configs?.twilio_service_enabled?.value === '1') && configs?.twilio_module?.value && ( <> {t('OR', 'Or')} )} {configs && Object.keys(configs).length > 0 ? ( (((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)) && !isGuest && ( <> {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} /> )} ) ) : ( )} {enabledPoweredByOrdering && ( Powered By Ordering.co )} setIsModalVisible(false)} entireModal title={t('VERIFY_PHONE', 'Verify Phone')} > setIsModalVisible(false)} /> setWillVerifyOtpState(false)} animationType='slide' > ); }; export const LoginForm = (props: any) => { const loginProps = { ...props, isRecaptchaEnable: true, UIComponent: LoginFormUI, }; return ; };