diff options
| author | meganhong <34787696+meganhong@users.noreply.github.com> | 2020-07-13 15:08:06 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-13 18:08:06 -0400 |
| commit | 5dee1e585353b6d7407f521dfa9186dbf10e8226 (patch) | |
| tree | 752f2053ec50f817a44c90501f3594427d50af5a /src/screens/onboarding/Login.tsx | |
| parent | 95e160e64dc6a5763fdbdc7d7e5b814302446ba9 (diff) | |
TMA123: Add Profile Pictures UI (#17)
* rebasing
* rebasing
* remove debug code
* fixed margins and added navigation from login
* moved plist file into gitignore
* moved index.ts to onboarding directory
* install react native image crop picker
* added permissions into Info.plist
* rebasing
* minor changes for Justins PR
* change debug code back
Co-authored-by: meganhong <meganhong31@g.ucla.edu>
Diffstat (limited to 'src/screens/onboarding/Login.tsx')
| -rw-r--r-- | src/screens/onboarding/Login.tsx | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx new file mode 100644 index 00000000..c06f6f27 --- /dev/null +++ b/src/screens/onboarding/Login.tsx @@ -0,0 +1,317 @@ +import React, {useRef} from 'react'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import { + View, + Text, + Alert, + StatusBar, + Image, + TouchableOpacity, + StyleSheet, + KeyboardAvoidingView, + Platform, +} from 'react-native'; + +import {RootStackParamList} from '../../routes'; +import {Background, TaggInput, SubmitButton} from '../../components'; +import {usernameRegex, LOGIN_ENDPOINT} from '../../constants'; + +type VerificationScreenRouteProp = RouteProp<RootStackParamList, 'Login'>; +type VerificationScreenNavigationProp = StackNavigationProp< + RootStackParamList, + 'Login' +>; +interface LoginProps { + route: VerificationScreenRouteProp; + navigation: VerificationScreenNavigationProp; +} +/** + * 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: false, + isValidPassword: false, + attemptedSubmit: false, + }); + + /** + * 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.length >= 6; + 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. + */ + const handleLogin = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + if (form.isValidUser && form.isValidPassword) { + let response = await fetch(LOGIN_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + username: form.username, + password: form.password, + }), + }); + + let statusCode = response.status; + if (statusCode === 200) { + Alert.alert('Successfully logged in! 🥳', `Welcome ${form.username}`); + } else if (statusCode === 401) { + Alert.alert( + 'Login failed 😔', + 'Try re-entering your login information.', + ); + } else { + Alert.alert( + 'Something went wrong! ðŸ˜', + "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( + 'Looks like our servers are down. 😓', + "Try again in a couple minutes. We're sorry for the inconvenience.", + ); + return { + name: 'Login error', + description: error, + }; + } + }; + + /* + * 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 = () => ( + <SubmitButton + text="Let's Start!" + color="#fff" + style={styles.button} + accessibilityLabel="Let's Start!" + accessibilityHint="Select this after entering your tagg username and password" + onPress={handleLogin} + /> + ); + + /** + * 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 centered style={styles.container}> + <StatusBar barStyle="light-content" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.keyboardAvoidingView}> + <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} + /> + + <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 /> + </KeyboardAvoidingView> + <RegistrationPrompt /> + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + keyboardAvoidingView: { + alignItems: 'center', + }, + logo: { + width: 215, + height: 149, + marginBottom: '10%', + }, + forgotPassword: { + marginTop: 10, + marginBottom: 15, + }, + forgotPasswordText: { + fontSize: 14, + color: '#fff', + textDecorationLine: 'underline', + }, + start: { + width: 144, + height: 36, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#fff', + borderRadius: 18, + marginBottom: '15%', + }, + startDisabled: { + backgroundColor: '#ddd', + }, + startText: { + fontSize: 16, + color: '#78a0ef', + fontWeight: 'bold', + }, + newUserContainer: { + flexDirection: 'row', + color: '#fff', + }, + newUser: { + fontSize: 14, + color: '#f4ddff', + }, + getStarted: { + fontSize: 14, + color: '#fff', + textDecorationLine: 'underline', + }, + button: { + marginVertical: '10%' + } +}); + +export default Login; |
