aboutsummaryrefslogtreecommitdiff
path: root/src/screens/Login.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/Login.tsx')
-rw-r--r--src/screens/Login.tsx325
1 files changed, 185 insertions, 140 deletions
diff --git a/src/screens/Login.tsx b/src/screens/Login.tsx
index 5291b643..193ef767 100644
--- a/src/screens/Login.tsx
+++ b/src/screens/Login.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useRef} from 'react';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import {
@@ -9,70 +9,74 @@ import {
Image,
TouchableOpacity,
StyleSheet,
- Keyboard,
- TouchableWithoutFeedback,
} from 'react-native';
-import {RootStackParams} from '../routes';
-import LinearGradient from 'react-native-linear-gradient';
-
-import LoginInput from '../components/common/LoginInput';
-
-import * as Constants from '../constants';
-
-type LoginScreenRouteProp = RouteProp<RootStackParams, 'Login'>;
-type LoginScreenNavigationProp = StackNavigationProp<RootStackParams, 'Login'>;
+import {RootStackParamList} from '../routes';
+import {Background, TaggInput, CenteredView} from '../components';
+import {usernameRegex, LOGIN_ENDPOINT} from '../constants';
+type LoginScreenRouteProp = RouteProp<RootStackParamList, 'Login'>;
+type LoginScreenNavigationProp = StackNavigationProp<
+ RootStackParamList,
+ 'Login'
+>;
interface LoginProps {
route: LoginScreenRouteProp;
navigation: LoginScreenNavigationProp;
}
-
-const Login = ({navigation}: LoginProps) => {
- const input_ref = React.createRef();
- const [data, setData] = React.useState({
+/**
+ * Login screen.
+ * @param navigation react-navigation navigation object.
+ */
+const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
+ // ref for focusing on input fields
+ const inputRef = useRef();
+ // login form state
+ const [form, setForm] = React.useState({
username: '',
password: '',
- isValidUser: true,
- isValidPassword: true,
+ isValidUser: false,
+ isValidPassword: false,
+ attemptedSubmit: false,
});
- /*
- Updates the state of username. Also verifies the input of the Username field.
- */
+ /**
+ * Updates the state of username. Also verifies the input of the username field by ensuring proper length and characters.
+ */
const handleUsernameUpdate = (val: string) => {
- let validLength: boolean = val.trim().length >= 6;
+ let validLength: boolean = val.length >= 6;
+ let validChars: boolean = usernameRegex.test(val);
- if (validLength) {
- setData({
- ...data,
+ if (validLength && validChars) {
+ setForm({
+ ...form,
username: val,
isValidUser: true,
});
} else {
- setData({
- ...data,
+ setForm({
+ ...form,
username: val,
isValidUser: false,
});
}
};
- /*
- Updates the state of password. Also verifies the input of the Password field.
- */
+ /**
+ * 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) {
- setData({
- ...data,
+ setForm({
+ ...form,
password: val,
isValidPassword: true,
});
} else {
- setData({
- ...data,
+ setForm({
+ ...form,
password: val,
isValidPassword: false,
});
@@ -80,23 +84,39 @@ const Login = ({navigation}: LoginProps) => {
};
/*
- Handler for the Let's Start button or the Go button on the keyboard.
+ * 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.
- */
+ */
const handleLogin = async () => {
+ if (!form.attemptedSubmit) {
+ setForm({
+ ...form,
+ attemptedSubmit: true,
+ });
+ }
try {
- if (data.isValidUser && data.isValidPassword) {
- let response = await fetch(Constants.LOGIN_ENDPOINT, {
+ if (form.isValidUser && form.isValidPassword) {
+ let response = await fetch(LOGIN_ENDPOINT, {
method: 'POST',
body: JSON.stringify({
- username: data.username,
- password: data.password,
+ username: form.username,
+ password: form.password,
}),
});
let statusCode = response.status;
if (statusCode === 200) {
- Alert.alert('Successfully logged in! 🥳', `Welcome ${data.username}`);
+ Alert.alert('Successfully logged in! 🥳', `Welcome ${form.username}`);
} else if (statusCode === 401) {
Alert.alert(
'Login failed 😔',
@@ -108,6 +128,9 @@ const Login = ({navigation}: LoginProps) => {
"Would you believe me if I told you that I don't know what happened?",
);
}
+ } else {
+ setForm({...form, attemptedSubmit: false});
+ setTimeout(() => setForm({...form, attemptedSubmit: true}));
}
} catch (error) {
Alert.alert(
@@ -122,135 +145,157 @@ const Login = ({navigation}: LoginProps) => {
};
/*
- Handler for the submit button on the Username keyboard
- */
- const handleUsernameSubmit = () => {
- input_ref.current.focus();
- };
-
- const handleRegistration = () => {
+ * Handles tap on "Get Started" text by resetting fields & navigating to the registration page.
+ */
+ const goToRegistration = () => {
navigation.navigate('Registration');
+ setForm({...form, attemptedSubmit: false});
};
+ /**
+ * Login screen forgot password button.
+ */
+ const ForgotPassword = () => (
+ <TouchableOpacity
+ accessibilityLabel="Forgot password button"
+ accessibilityHint="Select this if you forgot your tagg password"
+ style={styles.forgotPassword}
+ onPress={() => Alert.alert("tagg! You're it!")}>
+ <Text style={styles.forgotPasswordText}>Forgot password</Text>
+ </TouchableOpacity>
+ );
+
+ /**
+ * Login screen login button.
+ */
+ const LoginButton = () => (
+ <TouchableOpacity
+ accessibilityLabel="Let's Start!"
+ accessibilityHint="Select this after entering your tagg username and password"
+ onPress={handleLogin}
+ style={styles.start}>
+ <Text style={styles.startText}>Let's Start!</Text>
+ </TouchableOpacity>
+ );
+
+ /**
+ * Login screen registration prompt.
+ */
+ const RegistrationPrompt = () => (
+ <View style={styles.newUserContainer}>
+ <Text
+ accessible={true}
+ accessibilityLabel="New to tagg?"
+ style={styles.newUser}>
+ New to tagg?{' '}
+ </Text>
+ <TouchableOpacity
+ accessibilityLabel="Get started."
+ accessibilityHint="Select this if you do not have a tagg account">
+ <Text
+ accessible={true}
+ accessibilityLabel="Get started"
+ style={styles.getStarted}
+ onPress={goToRegistration}>
+ Get started!
+ </Text>
+ </TouchableOpacity>
+ </View>
+ );
+
return (
- <>
+ <Background>
<StatusBar barStyle="light-content" />
- <TouchableWithoutFeedback
- onPress={() => {
- Keyboard.dismiss();
- }}>
- <View style={styles.container}>
- <LinearGradient
- colors={['#8F00FF', '#6EE7E7']}
- style={styles.linearGradient}
- useAngle={true}
- angle={154.72}
- angleCenter={{x: 0.5, y: 0.5}}>
- <Image
- source={require('../assets/images/logo.png')}
- style={styles.logo}
- />
- <LoginInput
- type={data.username}
- isUsername={true}
- onChangeText={(user) => handleUsernameUpdate(user)}
- onSubmitEditing={() => handleUsernameSubmit()}
- isValid={data.isValidUser}
- validationWarning={'Username must be at least 6 characters long.'}
- />
- <LoginInput
- type={data.password}
- isPassword={true}
- onChangeText={(user) => handlePasswordUpdate(user)}
- onSubmitEditing={() => handleLogin()}
- isValid={data.isValidPassword}
- validationWarning={'Password must be at least 8 characters long.'}
- input_ref={input_ref}
- />
- <TouchableOpacity
- accessibilityLabel="Forgot password button"
- accessibilityHint="Select this if you forgot your tagg password"
- style={styles.forgotPassword}
- onPress={() => Alert.alert("tagg! You're it!")}>
- <Text style={styles.forgotPasswordText}>Forgot password</Text>
- </TouchableOpacity>
- <TouchableOpacity
- accessibilityLabel="Let's start button"
- accessibilityHint="Select this after entering your tagg username and password"
- style={styles.start}
- onPress={() => handleLogin()}>
- <Text style={styles.startText}>Let's Start!</Text>
- </TouchableOpacity>
- <Text
- accessible={true}
- accessibilityLabel="New to tagg?"
- style={styles.newUser}>
- New to tagg?{' '}
- <Text
- accessible={true}
- accessibilityLabel="Get started"
- accessibilityHint="Select this if you do not have a tagg account"
- style={styles.getStarted}
- onPress={() => handleRegistration()}>
- Get started!
- </Text>
- </Text>
- </LinearGradient>
- </View>
- </TouchableWithoutFeedback>
- </>
+ <CenteredView>
+ <Image
+ source={require('../assets/images/logo.png')}
+ style={styles.logo}
+ />
+ <TaggInput
+ accessibilityHint="Enter your tagg username here"
+ accessibilityLabel="Username text entry box"
+ placeholder="Username"
+ autoCompleteType="username"
+ textContentType="username"
+ returnKeyType="next"
+ autoCapitalize="none"
+ onChangeText={handleUsernameUpdate}
+ onSubmitEditing={handleUsernameSubmit}
+ blurOnSubmit={false}
+ valid={form.isValidUser}
+ invalidWarning="Username must be at least 6 characters and can only contain letters, numbers, periods, and underscores."
+ attemptedSubmit={form.attemptedSubmit}
+ width="100%"
+ />
+
+ <TaggInput
+ accessibilityHint="Enter your tagg password here"
+ accessibilityLabel="Password text entry box"
+ placeholder="Password"
+ autoCompleteType="password"
+ textContentType="password"
+ returnKeyType="go"
+ autoCapitalize="none"
+ secureTextEntry
+ onChangeText={handlePasswordUpdate}
+ onSubmitEditing={handleLogin}
+ valid={form.isValidPassword}
+ invalidWarning="Password must be at least 8 characters long."
+ attemptedSubmit={form.attemptedSubmit}
+ ref={inputRef}
+ />
+ <ForgotPassword />
+ <LoginButton />
+ <RegistrationPrompt />
+ </CenteredView>
+ </Background>
);
};
const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: 'transparent',
- },
- linearGradient: {
- flex: 1,
- alignItems: 'center',
- },
logo: {
- top: 165,
width: 215,
height: 149,
+ marginBottom: '10%',
},
forgotPassword: {
- top: 190,
- left: -60,
+ marginTop: 10,
+ marginBottom: 15,
},
forgotPasswordText: {
- fontSize: 15,
- color: '#FFFFFF',
+ fontSize: 14,
+ color: '#fff',
textDecorationLine: 'underline',
},
start: {
- top: 195,
width: 144,
height: 36,
justifyContent: 'center',
alignItems: 'center',
- backgroundColor: '#FFFFFF',
- borderRadius: 20,
- marginTop: 15,
+ backgroundColor: '#fff',
+ borderRadius: 18,
+ marginBottom: '10%',
+ },
+ startDisabled: {
+ backgroundColor: '#ddd',
},
startText: {
- fontSize: 15,
- color: '#78A0EF',
+ fontSize: 16,
+ color: '#78a0ef',
fontWeight: 'bold',
},
- getStarted: {
- color: '#FFFFFF',
- textDecorationLine: 'underline',
+ newUserContainer: {
+ flexDirection: 'row',
+ color: '#fff',
},
newUser: {
- top: 240,
- color: '#F4DDFF',
+ fontSize: 14,
+ color: '#f4ddff',
},
- invalidCredentials: {
- top: 180,
- color: '#F4DDFF',
+ getStarted: {
+ fontSize: 14,
+ color: '#fff',
+ textDecorationLine: 'underline',
},
});