import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Modal, TouchableOpacity, View } from 'react-native'; import { TextInput } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { Button } from '../../components/Button'; import Seperator from '../../components/Seperator'; import { Toast } from '../../components/Toast'; import { Actions } from '../../provider/Actions'; import { useTracked } from '../../provider/index'; import { DDS } from '../../services/DeviceDetection'; import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent, ToastEvent } from '../../services/EventManager'; import { clearMessage } from '../../services/Message'; import { validateEmail, validatePass, validateUsername } from '../../services/Validation'; import { getElevation } from '../../utils'; import { db } from '../../utils/DB'; import { eOpenLoginDialog, eOpenRecoveryKeyDialog, eStartSyncer, refreshNotesPage } from '../../utils/Events'; import { SIZE, WEIGHT } from '../../utils/SizeUtils'; import { sleep } from '../../utils/TimeUtils'; import { ActionIcon } from '../ActionIcon'; import BaseDialog from '../Dialog/base-dialog'; import DialogContainer from '../Dialog/dialog-container'; import { ListHeaderComponent } from '../SimpleList/ListHeaderComponent'; import Heading from '../Typography/Heading'; import Paragraph from '../Typography/Paragraph'; const MODES = { login: 0, signup: 1, forgotPassword: 2, changePassword: 3, }; const LoginDialog = () => { const [state, dispatch] = useTracked(); const colors = state.colors; const [visible, setVisible] = useState(false); const [status, setStatus] = useState(null); const [loggingIn, setLoggingIn] = useState(false); const [email, setEmail] = useState(null); const [password, setPassword] = useState(null); const [invalidEmail, setInvalidEmail] = useState(false); const [invalidPassword, setInvalidPassword] = useState(false); const [username, setUsername] = useState(null); const [invalidUsername, setInvalidUsername] = useState(false); const [secureEntry, setSecureEntry] = useState(true); const [confirmPassword, setConfirmPassword] = useState(false); const [oldPassword, setOldPassword] = useState(null); const [passwordReEnter, setPasswordReEnter] = useState(null); const [failed, setFailed] = useState(false); const [signingIn, setSigningIn] = useState(false); const [userConsent, setUserConsent] = useState(false); const [mode, setMode] = useState(MODES.login); const _email = useRef(); const _pass = useRef(); const _username = useRef(); const _oldPass = useRef(); const _oPass = useRef(); const _passConfirm = useRef(); const _passContainer = useRef(); const insets = useSafeAreaInsets(); const MODE_DATA = [ { headerButton: 'Login', headerButtonFunc: () => { setMode(MODES.signup); }, button: 'Login', buttonFunc: loginUser, headerParagraph: 'create a new account', showForgotButton: true, loading: 'Please wait while we log in and sync your data.', showLoader: true, }, { headerButton: 'Sign Up', headerButtonFunc: () => { setMode(MODES.login); }, button: 'Create Account', buttonFunc: signupUser, headerParagraph: 'login to your account', showForgotButton: false, loading: 'Please wait while we are setting up your account.', showLoader: true, }, { headerButton: 'Forgot Password', headerButtonFunc: () => { setMode(MODES.signup); }, button: 'Send Recovery Email', buttonFunc: sendEmail, headerParagraph: 'login to your account', showForgotButton: false, loading: 'We have sent you a recovery email on ' + email + '. Follow the link in the email to set a new password', showLoader: false, }, { headerButton: 'Change Password', headerButtonFunc: () => { setMode(MODES.signup); }, button: 'Change Password', buttonFunc: changePassword, headerParagraph: 'login to your account', showForgotButton: false, loading: 'Please wait while we change your password and encrypt your data.', showLoader: true, }, ]; const current = MODE_DATA[mode]; useEffect(() => { eSubscribeEvent(eOpenLoginDialog, open); return () => { eUnSubscribeEvent(eOpenLoginDialog, open); }; }, []); function open(mode) { setMode(mode? mode : MODES.login); setVisible(true); } const close = () => { if (loggingIn || signingIn) return; _email.current?.clear(); _pass.current?.clear(); _passConfirm.current?.clear(); _username.current?.clear(); setVisible(false); setUsername(null); setPassword(null); setConfirmPassword(null); setUserConsent(false); setEmail(null); setLoggingIn(false); setMode(MODES.login); }; const loginUser = async () => { if ( !password || password.length < 8 || !username || invalidPassword || invalidUsername ) { ToastEvent.show('username or password is invalid', 'error', 'local'); return; } setLoggingIn(true); _username.current.blur(); _pass.current.blur(); setStatus('Logging in'); try { await db.user.login(username.toLowerCase(), password, true); } catch (e) { ToastEvent.show(e.message, 'error', 'local'); setLoggingIn(false); return; } try { let user = await db.user.get(); if (!user) throw new Error('Username or passoword incorrect!'); setStatus('Syncing Data'); dispatch({type: Actions.USER, user: user}); await db.sync(); eSendEvent(eStartSyncer); dispatch({type: Actions.ALL}); eSendEvent(refreshNotesPage); clearMessage(dispatch); close(); ToastEvent.show(`Logged in as ${username}`, 'success', 'local'); } catch (e) { console.warn(e); ToastEvent.show(e.message, 'error', 'local'); } finally { setLoggingIn(false); } }; const validateInfo = () => { if (!password || !email || !username || !passwordReEnter) { ToastEvent.show('All fields are required', 'error', 'local'); return false; } if (!confirmPassword) { ToastEvent.show('Passwords do not match', 'error', 'local'); return false; } if (invalidEmail && invalidPassword && invalidUsername) { ToastEvent.show('Signup information is invalid', 'error', 'local'); return false; } if (!userConsent) { ToastEvent.show( 'You must agree to our terms of service and privacy policy.', 'error', 'local', 5000, () => { setUserConsent(true); signupUser(); ToastEvent.hide(); }, 'I Agree', ); return false; } return true; }; const signupUser = async () => { if (!validateInfo()) return; setSigningIn(true); setStatus('Creating User'); try { await db.user.signup(username, email, password); } catch (e) { setSigningIn(false); setFailed(true); ToastEvent.show('Signup failed, Network Error', 'error', 'local'); return; } let user; try { user = await db.user.get(); setStatus('Setting up crenditials'); dispatch({type: Actions.USER, user: user}); eSendEvent(eStartSyncer); clearMessage(dispatch); close(); await sleep(500); eSendEvent(eOpenRecoveryKeyDialog, true); } catch (e) { setFailed(true); ToastEvent.show('Login Failed, try again', 'error', 'local'); } finally { setSigningIn(false); } }; const sendEmail = () => { setStatus('Recovery Email Sent!'); // handle recovery email sending }; const changePassword = () => {}; return !visible ? null : ( {status ? ( { if (!current.showLoader) { setStatus(null); } }}> {status} {current.loading} {!current.showLoader ? null : ( {' '} Do not close the app. )} {!current.showLoader ? null : ( )} ) : null} {DDS.isLargeTablet() ? ( ) : null} {DDS.isLargeTablet() ? ( ) : ( { close(); }} customStyle={{ width: 40, height: 40, marginLeft: -5, }} color={colors.heading} /> )} {mode === MODES.forgotPassword || mode === MODES.changePassword ? null : ( <> { if (!invalidUsername) { _username.current?.setNativeProps({ style: { borderColor: colors.accent, }, }); } }} editable={!loggingIn || !signingIn} autoCapitalize="none" defaultValue={username} onBlur={() => { if (!validateUsername(username) && username?.length > 0) { setInvalidUsername(true); _username.current?.setNativeProps({ style: { color: colors.errorText, borderColor: colors.errorText, }, }); } else { setInvalidUsername(false); _username.current?.setNativeProps({ style: { borderColor: colors.nav, }, }); } }} textContentType="username" onChangeText={(value) => { setUsername(value); if (invalidUsername && validateUsername(username)) { setInvalidUsername(false); _username.current.setNativeProps({ style: { color: colors.pri, borderColor: colors.accent, }, }); } }} onSubmitEditing={() => { if (!validateUsername(username)) { setInvalidUsername(true); _username.current.setNativeProps({ style: { color: colors.errorText, }, }); } _pass.current?.focus(); }} blurOnSubmit={false} style={{ paddingHorizontal: 0, height: 50, borderBottomWidth: 1, borderColor: colors.nav, fontSize: SIZE.md, fontFamily: WEIGHT.regular, color: colors.pri, }} placeholder="Username (a-z _- 0-9)" placeholderTextColor={colors.icon} /> {invalidUsername ? ( {' '} Username is invalid ) : null} )} {mode !== MODES.signup ? null : ( <> { if (!invalidEmail) { _email.current.setNativeProps({ style: { borderColor: colors.accent, }, }); } }} editable={!loggingIn || !signingIn} autoCapitalize="none" defaultValue={email} onBlur={() => { if (!validateEmail(email) && email?.length > 0) { setInvalidEmail(true); _email.current?.setNativeProps({ style: { color: colors.errorText, borderColor: colors.errorText, }, }); } else { setInvalidEmail(false); _email.current?.setNativeProps({ style: { borderColor: colors.nav, }, }); } }} textContentType="emailAddress" onChangeText={(value) => { setEmail(value); if (invalidEmail && validateEmail(email)) { setInvalidEmail(false); _email.current.setNativeProps({ style: { color: colors.pri, borderColor: colors.accent, }, }); } }} blurOnSubmit={false} onSubmitEditing={() => { if (!validateEmail(email)) { setInvalidEmail(true); _email.current.setNativeProps({ style: { color: colors.errorText, }, }); } if (mode === MODES.signup) { _pass.current?.focus(); } }} style={{ paddingHorizontal: 0, height: 50, borderBottomWidth: 1, borderColor: colors.nav, fontSize: SIZE.md, fontFamily: WEIGHT.regular, color: colors.pri, }} placeholder="youremail@gmail.com" placeholderTextColor={colors.icon} /> {invalidEmail ? ( {' '} Email is invalid ) : null} )} {mode !== MODES.changePassword ? null : ( <> { setOldPassword(value); }} onSubmitEditing={() => { if (mode === MODES.changePassword) { _pass.current?.focus(); } }} style={{ paddingHorizontal: 0, height: 50, fontSize: SIZE.md, fontFamily: WEIGHT.regular, width: '85%', maxWidth: '85%', color: colors.pri, }} secureTextEntry={secureEntry} placeholder="Current Password" placeholderTextColor={colors.icon} /> { setSecureEntry(!secureEntry); }} style={{ width: 25, }} color={secureEntry ? colors.icon : colors.accent} /> {invalidPassword ? ( {' '} Password is invalid ) : null} )} {mode === MODES.forgotPassword ? null : ( <> { if (!invalidPassword) { _passContainer.current?.setNativeProps({ style: { borderColor: colors.accent, }, }); } }} editable={!loggingIn || !signingIn} autoCapitalize="none" defaultValue={password} onBlur={() => { if (!validatePass(password) && password?.length > 0) { setInvalidPassword(true); _pass.current?.setNativeProps({ style: { color: colors.errorText, }, }); _passContainer.current?.setNativeProps({ style: { borderColor: colors.errorText, }, }); } else { setInvalidPassword(false); _passContainer.current?.setNativeProps({ style: { borderColor: colors.nav, }, }); } }} onChangeText={(value) => { setPassword(value); if (invalidPassword && validatePass(password)) { setInvalidPassword(false); _pass.current.setNativeProps({ style: { color: colors.pri, }, }); } }} onSubmitEditing={() => { if (!validatePass(password)) { setInvalidPassword(true); _pass.current.setNativeProps({ style: { color: colors.errorText, }, }); } if ( mode === MODES.signup || mode === MODES.changePassword ) { _passConfirm.current?.focus(); } else { current.buttonFunc(); } }} blurOnSubmit={false} style={{ paddingHorizontal: 0, height: 50, fontSize: SIZE.md, fontFamily: WEIGHT.regular, width: '85%', maxWidth: '85%', color: colors.pri, }} secureTextEntry={secureEntry} placeholder="Password (6+ characters)" placeholderTextColor={colors.icon} /> { setSecureEntry(!secureEntry); }} style={{ width: 25, }} color={secureEntry ? colors.icon : colors.accent} /> {mode === MODES.login ? ( { setMode(MODES.forgotPassword); }} style={{ alignSelf: 'flex-end', marginTop: 2.5, }}> Forgot password? ) : null} {invalidPassword ? ( {' '} Password is invalid ) : null} )} {mode !== MODES.signup && mode !== MODES.changePassword ? null : ( <> { setPasswordReEnter(value); if (value !== password) { setConfirmPassword(false); _passConfirm.current.setNativeProps({ style: { borderColor: colors.errorText, }, }); _pass.current.setNativeProps({ style: { borderColor: colors.errorText, }, }); } else { setConfirmPassword(true); _passConfirm.current.setNativeProps({ style: { borderColor: colors.accent, }, }); _pass.current.setNativeProps({ style: { borderColor: colors.accent, }, }); } }} onFocus={() => { _passConfirm.current.setNativeProps({ style: { borderColor: colors.accent, }, }); }} onSubmitEditing={() => { current.buttonFunc(); }} blurOnSubmit style={{ paddingHorizontal: 0, borderBottomWidth: 1, height: 50, borderColor: colors.nav, fontSize: SIZE.md, fontFamily: WEIGHT.regular, color: colors.pri, }} secureTextEntry={secureEntry} placeholder="Confirm Password" placeholderTextColor={colors.icon} /> {password && !invalidPassword && !confirmPassword ? ( {' '} Passwords do not match ) : null} )} {mode !== MODES.signup ? null : ( <> { setUserConsent(!userConsent); }} activeOpacity={0.7} style={{ flexDirection: 'row', width: '100%', alignItems: 'center', height: 40, }}> By signing up you agree to our{' '} terms of service{' '} and{' '} privacy policy. )} {mode !== MODES.signup ? null : }