Skip to content

Instantly share code, notes, and snippets.

@kevincarpdev
Created April 10, 2023 13:13
Show Gist options
  • Save kevincarpdev/70d5a5b843ece6e110573558cd8df96f to your computer and use it in GitHub Desktop.
Save kevincarpdev/70d5a5b843ece6e110573558cd8df96f to your computer and use it in GitHub Desktop.
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