Created
April 10, 2023 13:13
-
-
Save kevincarpdev/70d5a5b843ece6e110573558cd8df96f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Dispatch, SetStateAction, useEffect } from 'react'; | |
import { View, Pressable, Keyboard, TouchableOpacity } from 'react-native'; | |
import { useNavigation } from '@react-navigation/native'; | |
import { Text } from 'react-native-paper'; | |
import { GLOBAL_STYLE as GS, icons } from '../../assets'; | |
import { useAuthContext } from '../../context/auth/auth.context'; | |
import { | |
BAlert, | |
BButton, | |
BCheckbox, | |
BIcon, | |
BInput, | |
BModal, | |
BModalFooter, | |
BModalHeader, | |
BSwitch, | |
SizedBox, | |
} from '../Common'; | |
import { ModalInterface } from '../Common/BModal'; | |
import { STACK_HOME_ROUTES_NAMES } from '../../router/HomeStack'; | |
import mobxStore from '../../store/RootStore'; | |
import ForgotPasswordModal from './ForgotPassword'; | |
import * as Keychain from 'react-native-keychain'; | |
import AsyncStorage from '@react-native-async-storage/async-storage'; | |
const LoginModal: React.FC<ModalInterface> = ({ | |
isVisible = false, | |
onClose, | |
}) => { | |
const { isAuthenticated } = useAuthContext(); | |
const [quickModalEnabled, setQuickModalEnabled] = React.useState(false); | |
const { hasQuickLoginEnabled, supportedBiometry } = useAuthContext(); | |
const [isOpenForgotPasswordModal, setIsOpenForgotPasswordModal] = | |
React.useState(false); | |
useEffect(() => { | |
async function isBiometric() { | |
if (isVisible && supportedBiometry && hasQuickLoginEnabled) { | |
setQuickModalEnabled(true); | |
} | |
} | |
isBiometric(); | |
}, [isVisible, hasQuickLoginEnabled, supportedBiometry]); | |
const triggerForgotPasswordModal = () => { | |
if (isOpenForgotPasswordModal) { | |
setTimeout(() => { | |
mobxStore.setShowLoginModal('forgot-password'); | |
}, 500); | |
} | |
}; | |
return ( | |
<React.Fragment> | |
{isAuthenticated ? ( | |
<></> | |
) : ( | |
<BModal | |
isVisible={isVisible} | |
onClose={onClose} | |
onModalHide={triggerForgotPasswordModal}> | |
<React.Fragment> | |
<BModalHeader> | |
<BIcon | |
icon={icons.logo} | |
style={{ | |
height: 50, | |
width: 130, | |
resizeMode: 'contain', | |
}} | |
/> | |
</BModalHeader> | |
{/* CHOOSE VIEW */} | |
{quickModalEnabled ? ( | |
<QuickModalView | |
setQuickModalEnabled={setQuickModalEnabled} | |
closeModal={onClose} | |
/> | |
) : ( | |
<LoginView | |
setQuickModalEnabled={setQuickModalEnabled} | |
closeModal={onClose} | |
setIsOpenForgotPasswordModal={setIsOpenForgotPasswordModal} | |
/> | |
)} | |
</React.Fragment> | |
</BModal> | |
)} | |
</React.Fragment> | |
); | |
}; | |
export const LoginView = ({ | |
setQuickModalEnabled, | |
closeModal, | |
setIsOpenForgotPasswordModal, | |
}: { | |
setQuickModalEnabled: Dispatch<SetStateAction<boolean>>; | |
setIsOpenForgotPasswordModal: Dispatch<SetStateAction<boolean>>; | |
closeModal: () => void; | |
}) => { | |
const { login, supportedBiometry, hasQuickLoginEnabled } = useAuthContext(); | |
const navigation = useNavigation(); | |
const [username, setUsername] = React.useState(''); | |
const [password, setPassword] = React.useState(''); | |
const [isLoading, setIsLoading] = React.useState(false); | |
const [errorMessage, setErrorMessage] = React.useState<string | null>(null); | |
const [isRemember, setIsRemember] = React.useState(false); | |
const __handleLogin = async () => { | |
Keyboard.dismiss(); | |
setErrorMessage(null); | |
setIsLoading(true); | |
const login_res = await login({ username, password }); | |
if (login_res.result) { | |
closeModal(); | |
// navigation.navigate('ACCOUNT__LOGGED_IN' as never); | |
mobxStore.setGlobalToast({ | |
mode: 'success', | |
message: "You're now connected", | |
}); | |
if (isRemember) { | |
await AsyncStorage.setItem('rememberMe', username); | |
} | |
mobxStore.setShowBalance(true); | |
} else if (login_res.error) { | |
console.log(login_res.error); | |
setErrorMessage( | |
login_res?.error?.response?.data?.error?.errorMessage?.error | |
?.errorMessage || | |
login_res?.error?.response?.data?.error?.errorMessage || | |
'Something went wrong.', | |
); | |
} | |
setIsLoading(false); | |
}; | |
// Remember me | |
const toggleSwitch = async () => { | |
setIsRemember(!isRemember); | |
if (!isRemember) { | |
await AsyncStorage.setItem('rememberMe', username); | |
} else { | |
await AsyncStorage.removeItem('rememberMe'); | |
} | |
}; | |
const getRememberedUser = async () => { | |
try { | |
const username = await AsyncStorage.getItem('rememberMe'); | |
if (username !== null) { | |
setUsername(username); | |
setIsRemember(true); | |
return username; | |
} | |
} catch (error) { | |
// Error retrieving data | |
} | |
}; | |
React.useEffect(() => { | |
(async () => { | |
await getRememberedUser(); | |
})(); | |
return () => { | |
// Component unmount code. | |
}; | |
}, []); | |
return ( | |
<React.Fragment> | |
<View style={{ paddingVertical: 20, paddingHorizontal: 15 }}> | |
<View style={[GS.row, GS.alignCenter, GS.justifyCenter, GS.mb3]}> | |
<BIcon | |
icon={icons.user_account} | |
style={{ height: 20, width: 20, resizeMode: 'contain' }} | |
/> | |
<SizedBox width={10} /> | |
<Text | |
style={[GS.txtUpper, GS.txtLg, GS.FF_PoppinsSemiBold, GS.txtWhite]}> | |
Account login | |
</Text> | |
</View> | |
{/* */} | |
<BInput | |
placeholder="Username/Email" | |
value={username} | |
onChangeText={setUsername} | |
/> | |
{/* */} | |
<SizedBox height={15} /> | |
{/* */} | |
<BInput | |
placeholder="Password" | |
value={password} | |
onChangeText={setPassword} | |
password | |
loginShowPassword | |
/> | |
<View style={[GS.row, GS.alignCenter, GS.mt3]}> | |
<View style={GS.flex1}> | |
<BCheckbox | |
label="Remember me" | |
value={isRemember} | |
onValueChange={() => { | |
toggleSwitch(); | |
}} | |
/> | |
</View> | |
<TouchableOpacity | |
onPress={() => { | |
closeModal(); | |
setIsOpenForgotPasswordModal(true); | |
setTimeout(() => { | |
setIsOpenForgotPasswordModal(false); | |
}, 2000); | |
}}> | |
<Text style={[GS.txtUpper, GS.txtSm, GS.txtWhite]}> | |
Forgot password | |
</Text> | |
</TouchableOpacity> | |
</View> | |
{errorMessage && ( | |
<View style={{ marginTop: 20 }}> | |
<BAlert | |
dismissable={false} | |
mode="danger" | |
message={errorMessage} | |
setMessage={() => setErrorMessage(null)} | |
/> | |
</View> | |
)} | |
<SizedBox height={20} /> | |
{/* LOGIN BUTTON */} | |
<BButton | |
title="Login" | |
onPress={__handleLogin} | |
mode="primary" | |
loading={isLoading} | |
disabled={ | |
isLoading || !username.trim().length || !password.trim().length || mobxStore.disableLoginButton | |
} | |
/> | |
{supportedBiometry != null && ( | |
<> | |
<SizedBox height={5} /> | |
{/* */} | |
<Text style={[GS.txtCenter, GS.txtUpper, GS.my2, GS.txtSm]}> | |
USE BIOMETRIC IDENTIFICATION FOR QUICK LOGIN | |
</Text> | |
{/* QUICK LOGIN BUTTON */} | |
<BButton | |
title="Quick Login" | |
disabled={!hasQuickLoginEnabled || mobxStore.disableLoginButton} | |
onPress={() => setQuickModalEnabled(true)} | |
/> | |
</> | |
)} | |
</View> | |
<BModalFooter> | |
<React.Fragment> | |
<Text style={[GS.txtCenter, GS.txtUpper, GS.txtWhite]}> | |
Not a member yet? | |
</Text> | |
<SizedBox height={10} /> | |
<BButton | |
title="Register" | |
disabled={mobxStore.disableLoginButton} | |
onPress={() => { | |
closeModal(); | |
setTimeout(() => { | |
navigation.navigate( | |
STACK_HOME_ROUTES_NAMES.REGISTRATION as never, | |
); | |
}, 250); | |
}} | |
mode="danger" | |
/> | |
</React.Fragment> | |
</BModalFooter> | |
</React.Fragment> | |
); | |
}; | |
const QuickModalView = ({ | |
setQuickModalEnabled, | |
closeModal, | |
}: { | |
setQuickModalEnabled: Dispatch<SetStateAction<boolean>>; | |
closeModal: () => void; | |
}) => { | |
const { supportedBiometry, login } = useAuthContext(); | |
const handleBioLogin = () => { | |
try { | |
Keychain.getGenericPassword({ | |
authenticationType: | |
Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS, | |
}).then((creds: false | Keychain.UserCredentials) => { | |
if (creds) { | |
login({ | |
username: creds.username, | |
password: creds.password, | |
}).then((loginRes) => { | |
if (loginRes.result) { | |
closeModal(); | |
// navigation.navigate('ACCOUNT__LOGGED_IN' as never); | |
mobxStore.setGlobalToast({ | |
mode: 'success', | |
message: "You're now connected", | |
}); | |
} else if (loginRes.error) { | |
setQuickModalEnabled(false); | |
mobxStore.setGlobalToast({ | |
mode: 'danger', | |
message: | |
'Error on quick login, please try again using username/password', | |
}); | |
} | |
}); | |
} else { | |
setQuickModalEnabled(false); | |
mobxStore.setGlobalToast({ | |
mode: 'danger', | |
message: | |
'Error on quick login, please try again using username/password', | |
}); | |
} | |
}); | |
} catch (error) { | |
console.log("Keychain couldn't be accessed!", error); | |
} | |
}; | |
return ( | |
<React.Fragment> | |
<View style={{ paddingVertical: 20, paddingHorizontal: 15 }}> | |
<View style={[GS.row, GS.alignCenter, GS.justifyCenter]}> | |
<BIcon | |
icon={icons.user_account} | |
style={{ height: 20, width: 20, resizeMode: 'contain' }} | |
/> | |
<SizedBox width={10} /> | |
<Text | |
style={[GS.txtUpper, GS.txtLg, GS.FF_PoppinsSemiBold, GS.txtWhite]}> | |
Quick Login | |
</Text> | |
</View> | |
<View | |
style={{ | |
flexDirection: 'row', | |
justifyContent: 'center', | |
marginTop: 30, | |
marginBottom: 10, | |
}}> | |
{supportedBiometry === Keychain.BIOMETRY_TYPE.FACE_ID ? ( | |
<Pressable onPress={handleBioLogin}> | |
<BIcon | |
icon={icons.faceid_symbol} | |
style={{ height: 86, width: 86, resizeMode: 'contain' }} | |
/> | |
</Pressable> | |
) : ( | |
<Pressable onPress={handleBioLogin}> | |
<BIcon | |
icon={icons.touch_login_symbol} | |
style={{ height: 86, width: 86, resizeMode: 'contain' }} | |
/> | |
</Pressable> | |
)} | |
</View> | |
<SizedBox height={20} /> | |
</View> | |
<BModalFooter> | |
<React.Fragment> | |
<Text style={[GS.txtCenter, GS.txtUpper, GS.txtSm, GS.txtWhite]}> | |
USE THE STANDARDISED LOGIN METHOD | |
</Text> | |
<SizedBox height={10} /> | |
<BButton | |
title="Standard login" | |
onPress={() => { | |
setQuickModalEnabled(false); | |
}} | |
mode="primary" | |
type="contained" | |
/> | |
</React.Fragment> | |
</BModalFooter> | |
</React.Fragment> | |
); | |
}; | |
export default LoginModal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment