diff --git a/apps/mobile/src/components/LoginDialog/index.js b/apps/mobile/src/components/LoginDialog/index.js index e6ff596a5..c0249304a 100644 --- a/apps/mobile/src/components/LoginDialog/index.js +++ b/apps/mobile/src/components/LoginDialog/index.js @@ -1,137 +1,192 @@ -import React, {createRef} from 'react'; -import {Modal, TouchableOpacity, View} from 'react-native'; +import React, {createRef, useState} from 'react'; +import { + ActivityIndicator, + Clipboard, + Linking, + Modal, + Text, + TouchableOpacity, + View, +} from 'react-native'; import * as Animatable from 'react-native-animatable'; -import {normalize} from '../../common/common'; +import {TextInput} from 'react-native-gesture-handler'; +import QRCode from 'react-native-qrcode-generator'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import {opacity, ph, pv, SIZE, WEIGHT} from '../../common/common'; +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 {eSendEvent} from '../../services/eventManager'; -import {eLoginDialogNavigateBack, eSetModalNavigator} from '../../services/events'; -import {getElevation, DDS} from '../../utils/utils'; -import ForgotPassword from '../../views/ForgotPassword'; -import Login from '../../views/Login'; -import Signup from '../../views/Signup'; -import {updateEvent} from '../DialogManager/recievers'; -import {NavigationContainer} from '@react-navigation/native'; -import {createStackNavigator} from '@react-navigation/stack'; -import Container from '../Container'; +import {eCloseLoginDialog} from '../../services/events'; +import { + validateEmail, + validatePass, + validateUsername, +} from '../../services/validation'; +import {db, DDS, getElevation, ToastEvent} from '../../utils/utils'; +import CheckBox from '@react-native-community/checkbox'; -const Stack = createStackNavigator(); -const modalNavigatorRef2 = createRef(); +const LoginDialog = () => { + const [state, dispatch] = useTracked(); + const colors = state.colors; + const [visible, setVisible] = useState(true); + const [animated, setAnimated] = useState(false); + const [status, setStatus] = useState(''); + 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 [key, setKey] = useState(''); + const [passwordReEnter, setPasswordReEnter] = useState(null); + const [failed, setFailed] = useState(false); + const [signingIn, setSigningIn] = useState(false); + const [login, setLogin] = useState(true); + const [modalVisible, setModalVisible] = useState(false); + const [userConsent, setUserConsent] = useState(false); + const _email = createRef(); + const _pass = createRef(); + const _username = createRef(); + const _passConfirm = createRef(); + const _passContainer = createRef(); + + function open() { + setVisible(true); + } + + const close = () => { + setVisible(false); + setAnimated(false); + }; + + const loginUser = async () => { + if ( + !password || + password.length < 8 || + !username || + invalidPassword || + invalidUsername + ) { + ToastEvent.show('username or password is invalid', 'error'); + return; + } + setLoggingIn(true); + _username.current.blur(); + _pass.current.blur(); + setStatus('Logging in...'); + + try { + let res = await db.user.login(username.toLowerCase(), password); + console.log(res, username, password); + if (res) { + setStatus('Fetching data...'); + } + } catch (e) { + setTimeout(() => { + ToastEvent.show(e.message, 'error'); + setLoggingIn(false); + }, 500); + + return; + } + + let user; + + try { + user = await db.user.get(); + if (!user) throw new Error('Username or password incorrect'); + dispatch({type: ACTIONS.USER, user: user}); + dispatch({type: ACTIONS.SYNCING, syncing: true}); + setStatus('Syncing your notes...'); + await db.sync(); + eSendEvent(eStartSyncer); + navigation.goBack(); + dispatch({type: ACTIONS.ALL}); + eSendEvent(refreshNotesPage); + dispatch({type: ACTIONS.SYNCING, syncing: false}); + ToastEvent.show(`Logged in as ${username}`, 'success'); + } catch (e) { + dispatch({type: ACTIONS.SYNCING, syncing: false}); + setLoggingIn(false); + ToastEvent.show(e.message, 'error', 'local'); + } + }; + + const signupUser = async () => { + if (!validateInfo) return; + + setSigningIn(true); + setStatus('Creating your account...'); + 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.user.get(); + setStatus('Logging you in...'); + let k = await db.user.key(); + setKey(k.key); + setStatus('Setting up crenditials...'); + dispatch({type: ACTIONS.USER, user: user}); + eSendEvent(eStartSyncer); + setTimeout(() => { + setModalVisible(true); + }, 500); + } catch (e) { + setSigningIn(false); + setFailed(true); + ToastEvent.show('Login Failed, try again', 'error', 'local'); + } + }; -const ModalNavigator = ({onStateChange}) => { return ( - - { + setAnimated(true); + }} + onRequestClose={close} + visible={visible} + transparent={true}> + - - - - - - ); -}; - -class LoginDialog extends React.Component { - constructor(props) { - super(props); - this.state = { - visible: false, - animated: false, - }; - this.routeIndex = 0; - this.count = 0; - } - - open() { - updateEvent({type: ACTIONS.LOGIN_NAVIGATOR, enabled: true}); - this.setState({ - visible: true, - }); - } - - close() { - this.setState({ - visible: false, - animated: false, - }); - } - - render() { - const {visible, animated} = this.state; - const {colors} = this.props; - return ( - { - eSendEvent(eSetModalNavigator, true); - this.setState({ - animated: true, - }); - }} - onRequestClose={() => { - if (!this.routeIndex || this.routeIndex === 0) { - updateEvent({type: ACTIONS.LOGIN_NAVIGATOR, enabled: false}); - eSendEvent(eSetModalNavigator, false); - this.close(); - } else { - eSendEvent(eLoginDialogNavigateBack); - } - }} - visible={visible} - transparent={true}> - + {DDS.isTab ? ( this.close()} + onPress={close} style={{ width: '100%', height: '100%', @@ -139,30 +194,656 @@ class LoginDialog extends React.Component { zIndex: 1, }} /> + ) : null} + + + + + + Hi there! + + + All your data is encrypted. This means that we cannot read + your data or recover your password.{' '} + + If you lose your password, you can recover your data only + using your recovery key.{' '} + + - - - { - this.routeIndex = event.index; + + Take a Sceenshot of QR-Code + + + + + { + Clipboard.setString(key); + ToastEvent.show('Recovery key copied!', 'success', 'local'); + }} + style={{ + flexDirection: 'row', + borderWidth: 1, + borderRadius: 5, + paddingVertical: 8, + paddingHorizontal: 10, + marginTop: 15, + alignItems: 'center', + borderColor: colors.nav, + }}> + + {key} + + + + + + You can also save your recovery key from app settings on any + device. + + + { + DDS.isTab + ? eSendEvent(eCloseLoginDialog) + : navigation.navigate('Home'); + setModalVisible(false); + }} + activeOpacity={opacity} + style={{ + ...getElevation(5), + paddingVertical: pv + 5, + paddingHorizontal: ph, + borderRadius: 5, + width: '90%', + marginTop: 20, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.accent, + }}> + + I have saved the key + + + + + + + + <> + { + setVisible(false); + }} + style={{ + width: 50, + height: 50, + marginLeft: 12, + position: 'absolute', + textAlignVertical: 'center', + marginBottom: 15, + }} + color={colors.heading} + /> + + + + {login ? 'Login' : 'Sign up'} + {'\n'} + + {login + ? 'Get all your notes from other devices' + : 'Create an account to access notes anywhere.'} + {} + + + + + {login ? null : ( + <> + { + if (!invalidUsername) { + _username.current.setNativeProps({ + style: { + borderColor: colors.accent, + }, + }); + } + }} + 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, + }, + }); + } + }} + style={{ + paddingHorizontal: pv, + height: 50, + borderWidth: 1.5, + borderColor: colors.nav, + borderRadius: 5, + fontSize: SIZE.sm, + fontFamily: WEIGHT.regular, + }} + placeholder="Username (a-z _- 0-9)" + placeholderTextColor={colors.icon} + /> + {invalidUsername ? ( + + {' '} + Username is invalid + + ) : null} + + )} + + + + { + if (!invalidEmail) { + _email.current.setNativeProps({ + style: { + borderColor: colors.accent, + }, + }); + } }} + 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, + }, + }); + } + }} + onSubmitEditing={() => { + if (!validateEmail(email)) { + setInvalidEmail(true); + _email.current.setNativeProps({ + style: { + color: colors.errorText, + }, + }); + } + }} + style={{ + paddingHorizontal: pv, + height: 50, + borderWidth: 1.5, + borderColor: colors.nav, + borderRadius: 5, + fontSize: SIZE.sm, + fontFamily: WEIGHT.regular, + }} + placeholder="Email" + placeholderTextColor={colors.icon} /> - - - - - ); - } -} + + {invalidEmail ? ( + + {' '} + Password is invalid + + ) : null} + + + + + { + if (!invalidPassword) { + _passContainer.current?.setNativeProps({ + style: { + borderColor: colors.accent, + }, + }); + } + }} + 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, + }, + }); + } + }} + style={{ + paddingHorizontal: 0, + height: 50, + fontSize: SIZE.sm, + fontFamily: WEIGHT.regular, + width: '85%', + maxWidth: '85%', + }} + secureTextEntry={secureEntry} + placeholder="Password (6+ characters)" + placeholderTextColor={colors.icon} + /> + + { + setSecureEntry(!secureEntry); + }} + style={{ + width: 25, + }} + color={secureEntry ? colors.icon : colors.accent} + /> + + {invalidPassword ? ( + + {' '} + Password is invalid + + ) : null} + + + + {login ? 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, + }, + }); + }} + style={{ + paddingHorizontal: pv, + borderWidth: 1.5, + height: 50, + borderColor: colors.nav, + borderRadius: 5, + fontSize: SIZE.sm, + fontFamily: WEIGHT.regular, + }} + secureTextEntry={secureEntry} + placeholder="Confirm Password" + placeholderTextColor={colors.icon} + /> + + {password && !invalidPassword && !confirmPassword ? ( + + {' '} + Passwords do not match + + ) : null} + + )} + {login ? null : ( + + { + setUserConsent(value); + }} + boxType="circle" + tintColors={{true: colors.accent, false: colors.icon}} + value={userConsent} + /> + + By signing up you agree to our{' '} + + terms of service{' '} + + and{' '} + + privacy policy. + + + + )} + + + + +