import AsyncStorage from '@react-native-community/async-storage'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; import React, {useEffect, useRef} from 'react'; import { Alert, Image, KeyboardAvoidingView, Platform, StatusBar, StyleSheet, Text, TouchableOpacity, } from 'react-native'; import SplashScreen from 'react-native-splash-screen'; import {useDispatch, useSelector} from 'react-redux'; import {Background, TaggInput, TaggSquareButton} from '../../components'; import {LOGIN_ENDPOINT, usernameRegex} from '../../constants'; import { ERROR_DOUBLE_CHECK_CONNECTION, ERROR_FAILED_LOGIN_INFO, ERROR_INVALID_LOGIN, ERROR_LOGIN_FAILED, ERROR_NOT_ONBOARDED, ERROR_SOMETHING_WENT_WRONG_REFRESH, } from '../../constants/strings'; import {OnboardingStackParams} from '../../routes/onboarding'; import {fcmService} from '../../services'; import {RootState} from '../../store/rootReducer'; import {BackgroundGradientType, UniversityType} from '../../types'; import {normalize, userLogin} from '../../utils'; import UpdateRequired from './UpdateRequired'; type VerificationScreenRouteProp = RouteProp; type VerificationScreenNavigationProp = StackNavigationProp< OnboardingStackParams, 'Login' >; interface LoginProps { route: VerificationScreenRouteProp; navigation: VerificationScreenNavigationProp; } /** * Login screen. * @param navigation react-navigation navigation object. */ const Login: React.FC = ({navigation}: LoginProps) => { // ref for focusing on input fields const inputRef = useRef(); // login form state const [form, setForm] = React.useState({ username: '', password: '', isValidUser: false, isValidPassword: false, attemptedSubmit: false, token: '', }); const {newVersionAvailable} = useSelector((state: RootState) => state.user); /** * Redux Store stuff * Get the dispatch reference */ const dispatch = useDispatch(); /** * Hide the SplashScreen after the timeout. This is done to wait for AsyncStorage to get us the user from disk */ useEffect(() => { setTimeout(() => { SplashScreen.hide(); }, 100); }); /** * Updates the state of username. Also verifies the input of the username field by ensuring proper length and appropriate characters. */ const handleUsernameUpdate = (val: string) => { val = val.trim(); let validLength: boolean = val.length >= 3; let validChars: boolean = usernameRegex.test(val); if (validLength && validChars) { setForm({ ...form, username: val, isValidUser: true, }); } else { setForm({ ...form, username: val, isValidUser: false, }); } }; /** * Updates the state of password. Also verifies the input of the password field by ensuring proper length. */ const handlePasswordUpdate = (val: string) => { let validLength: boolean = val.trim().length >= 8; if (validLength) { setForm({ ...form, password: val, isValidPassword: true, }); } else { setForm({ ...form, password: val, isValidPassword: false, }); } }; /* * Handles tap on username keyboard's "Next" button by focusing on password field. */ const handleUsernameSubmit = () => { const passwordField: any = inputRef.current; if (passwordField) { passwordField.focus(); } }; /** * Handler for the Let's Start button or the Go button on the keyboard. Makes a POST request to the Django login API and presents Alerts based on the status codes that the backend returns. * Stores token received in the response, into client's AsynStorage */ const handleLogin = async () => { if (!form.attemptedSubmit) { setForm({ ...form, attemptedSubmit: true, }); } try { if (form.isValidUser && form.isValidPassword) { const {username, password} = form; let response = await fetch(LOGIN_ENDPOINT, { method: 'POST', body: JSON.stringify({ username, password, }), }); let statusCode = response.status; let data = await response.json(); if (statusCode === 200) { await AsyncStorage.setItem('token', data.token); await AsyncStorage.setItem('userId', data.UserID); await AsyncStorage.setItem('username', username); } if (statusCode === 200 && data.isOnboarded) { //Stores token received in the response into client's AsynStorage try { userLogin(dispatch, {userId: data.UserID, username}); fcmService.sendFcmTokenToServer(); } catch (err) { Alert.alert(ERROR_INVALID_LOGIN); } } else if ( statusCode === 200 && data.university === UniversityType.Empty ) { /** * A user account was created during onboarding step 2 but user didn't * finish step 3, thus does not have a universtiy. * Redirecting user back to onboarding to finish the process */ navigation.navigate('OnboardingStepThree', { userId: data.UserID, username: username, }); } else if (statusCode === 200 && !data.isOnboarded) { /** * A user account was created and finished the onboarding process but * did not have an invitation code at the time so the user's account * is not activated (isOnboarded) yet. */ navigation.navigate('InvitationCodeVerification', { userId: data.UserID, username: username, }); setTimeout(() => { Alert.alert(ERROR_NOT_ONBOARDED); }, 500); } else if (statusCode === 401) { Alert.alert(ERROR_FAILED_LOGIN_INFO); } else { Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } else { setForm({...form, attemptedSubmit: false}); setTimeout(() => setForm({...form, attemptedSubmit: true})); } } catch (error) { Alert.alert(ERROR_LOGIN_FAILED, ERROR_DOUBLE_CHECK_CONNECTION); return { name: 'Login error', description: error, }; } }; /* * Handles tap on "Get Started" text by resetting fields & navigating to the registration page. */ const startRegistrationProcess = () => { navigation.navigate('WelcomeScreen'); setForm({...form, attemptedSubmit: false}); }; /** * Login screen forgot password button. */ const ForgotPassword = () => ( navigation.navigate('PasswordResetRequest')}> Forgot password ); return ( {/* */} ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, keyboardAvoidingView: { alignItems: 'center', }, logo: { width: 215, height: 149, marginBottom: '10%', }, forgotPassword: { alignSelf: 'flex-start', marginVertical: '1%', borderBottomWidth: 1, paddingBottom: '1%', left: '3%', borderBottomColor: 'white', marginBottom: '8%', }, forgotPasswordText: { fontSize: normalize(14), color: '#fff', }, }); export default Login;