From f1300739189283929cb20a22e5281388d1bbeafc Mon Sep 17 00:00:00 2001 From: meganhong <34787696+meganhong@users.noreply.github.com> Date: Tue, 21 Jul 2020 09:51:47 -0700 Subject: Tma119: Split Registration Screen (#23) * split registration screen * styling * changed 4:2 to 3:3 * fade wizard with keyboard visibility * added regex for first and last name * accidentally saved videos in this folder * shortened fade duration to 300ms * add fade to Registration2 * rename RegistrationOne and RegistrationTwo * moved keyboard logic into RegistrationWizard * moved loading indicator out of the way * moving loading to outside of keyboard avoiding view * moved loading indicator up Co-authored-by: Megan Hong --- src/components/onboarding/RegistrationWizard.tsx | 59 ++- src/constants/regex.ts | 7 + src/routes/Routes.tsx | 17 +- src/screens/onboarding/Login.tsx | 2 +- src/screens/onboarding/Registration.tsx | 470 ----------------------- src/screens/onboarding/RegistrationOne.tsx | 268 +++++++++++++ src/screens/onboarding/RegistrationTwo.tsx | 375 ++++++++++++++++++ src/screens/onboarding/Verification.tsx | 2 +- src/screens/onboarding/index.ts | 3 +- 9 files changed, 715 insertions(+), 488 deletions(-) delete mode 100644 src/screens/onboarding/Registration.tsx create mode 100644 src/screens/onboarding/RegistrationOne.tsx create mode 100644 src/screens/onboarding/RegistrationTwo.tsx (limited to 'src') diff --git a/src/components/onboarding/RegistrationWizard.tsx b/src/components/onboarding/RegistrationWizard.tsx index 1157455f..31c3bbdf 100644 --- a/src/components/onboarding/RegistrationWizard.tsx +++ b/src/components/onboarding/RegistrationWizard.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {View, StyleSheet, ViewProps} from 'react-native'; +import {View, StyleSheet, ViewProps, Keyboard} from 'react-native'; +import * as Animatable from 'react-native-animatable'; interface RegistrationWizardProps extends ViewProps { step: 'one' | 'two' | 'three' | 'four' | 'five'; @@ -8,17 +9,53 @@ interface RegistrationWizardProps extends ViewProps { const RegistrationWizard = (props: RegistrationWizardProps) => { const stepStyle = styles.step; const stepActiveStyle = [styles.step, styles.stepActive]; + + // detects visibility of keyboard to display or hide wizard + const [keyboardVisible, setKeyboardVisible] = React.useState(false); + + Keyboard.addListener('keyboardDidShow', () => { + setKeyboardVisible(true); + }); + Keyboard.addListener('keyboardDidHide', () => { + setKeyboardVisible(false); + }); + return ( - - - - - - - - - + {!keyboardVisible && ( + + + + + + + + + + + + + + )} + {keyboardVisible && ( + + + + + + + + + + + + + + )} ); }; @@ -40,7 +77,7 @@ const styles = StyleSheet.create({ backgroundColor: '#e1f0ff', }, progress: { - width: '20%', + width: '16%', height: 2, backgroundColor: '#e1f0ff', }, diff --git a/src/constants/regex.ts b/src/constants/regex.ts index 350cb855..40c82691 100644 --- a/src/constants/regex.ts +++ b/src/constants/regex.ts @@ -19,3 +19,10 @@ export const passwordRegex: RegExp = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA * - match only alphanumerics, underscores, and periods */ export const usernameRegex: RegExp = /^[a-zA-Z0-9_.]{6,30}$/; + +/** + * The name regex has the following constraints + * - min. 2 chars, max. 20 chars ({2,20}) + * - match alphanumerics, apostrophes, commas, periods, dashes, and spaces + */ +export const nameRegex: RegExp = /^[A-Za-z'\-,. ]{2,20}$/; diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx index 63486448..7379c2c2 100644 --- a/src/routes/Routes.tsx +++ b/src/routes/Routes.tsx @@ -3,14 +3,18 @@ import {createStackNavigator} from '@react-navigation/stack'; import { Login, - Registration, + RegistrationOne, + RegistrationTwo, Verification, Profile, } from '../screens/onboarding'; export type RootStackParamList = { Login: undefined; - Registration: undefined; + RegistrationOne: undefined; + RegistrationTwo: + | {firstName: string; lastName: string; email: string} + | undefined; Verification: {username: string; email: string; userId: string}; Profile: {username: string; userId: string}; }; @@ -28,8 +32,13 @@ const Routes: React.FC = ({}) => { options={{headerShown: false}} /> + = ({navigation}: LoginProps) => { * Handles tap on "Get Started" text by resetting fields & navigating to the registration page. */ const goToRegistration = () => { - navigation.navigate('Registration'); + navigation.navigate('RegistrationOne'); setForm({...form, attemptedSubmit: false}); }; diff --git a/src/screens/onboarding/Registration.tsx b/src/screens/onboarding/Registration.tsx deleted file mode 100644 index 6cad92a5..00000000 --- a/src/screens/onboarding/Registration.tsx +++ /dev/null @@ -1,470 +0,0 @@ -import React, {useState, useRef} from 'react'; -import {RouteProp} from '@react-navigation/native'; -import {StackNavigationProp} from '@react-navigation/stack'; -import { - View, - Text, - StyleSheet, - StatusBar, - Alert, - Platform, - TouchableOpacity, - KeyboardAvoidingView, - ActivityIndicator, -} from 'react-native'; -import {usePromiseTracker, trackPromise} from 'react-promise-tracker'; - -import {RootStackParamList} from '../../routes'; -import { - ArrowButton, - RegistrationWizard, - TaggInput, - TermsConditions, - Background, -} from '../../components'; -import { - emailRegex, - passwordRegex, - usernameRegex, - REGISTER_ENDPOINT, - SEND_OTP_ENDPOINT, -} from '../../constants'; - -type RegistrationScreenRouteProp = RouteProp< - RootStackParamList, - 'Registration' ->; -type RegistrationScreenNavigationProp = StackNavigationProp< - RootStackParamList, - 'Registration' ->; -interface RegistrationProps { - route: RegistrationScreenRouteProp; - navigation: RegistrationScreenNavigationProp; -} -/** - * Registration screen. - * @param navigation react-navigation navigation object - */ -const Registration: React.FC = ({navigation}) => { - // refs for changing focus - const lnameRef = useRef(); - const emailRef = useRef(); - const usernameRef = useRef(); - const passwordRef = useRef(); - const confirmRef = useRef(); - /** - * Handles focus change to the next input field. - * @param field key for field to move focus onto - */ - const handleFocusChange = (field: string): void => { - switch (field) { - case 'lname': - const lnameField: any = lnameRef.current; - lnameField.focus(); - break; - case 'email': - const emailField: any = emailRef.current; - emailField.focus(); - break; - case 'username': - const usernameField: any = usernameRef.current; - usernameField.focus(); - break; - case 'password': - const passwordField: any = passwordRef.current; - passwordField.focus(); - break; - case 'confirm': - const confirmField: any = confirmRef.current; - confirmField.focus(); - break; - default: - return; - } - }; - - // registration form state - const [form, setForm] = useState({ - fname: '', - lname: '', - email: '', - username: '', - password: '', - confirm: '', - isValidFname: false, - isValidLname: false, - isValidEmail: false, - isValidUsername: false, - isValidPassword: false, - passwordsMatch: false, - tcAccepted: false, - attemptedSubmit: false, - }); - - /* - * Handles changes to the first name field value and verifies the input by updating state and running a validation function. - */ - const handleFnameUpdate = (fname: string) => { - let isValidFname: boolean = fname.length > 0; - setForm({ - ...form, - fname, - isValidFname, - }); - }; - /* - * Handles changes to the last name field value and verifies the input by updating state and running a validation function. - */ - const handleLnameUpdate = (lname: string) => { - let isValidLname: boolean = lname.length > 0; - setForm({ - ...form, - lname, - isValidLname, - }); - }; - /* - * Handles changes to the email field value and verifies the input by updating state and running a validation function. - */ - const handleEmailUpdate = (email: string) => { - let isValidEmail: boolean = emailRegex.test(email); - setForm({ - ...form, - email, - isValidEmail, - }); - }; - - /* - * Handles changes to the username field value and verifies the input by updating state and running a validation function. - */ - const handleUsernameUpdate = (username: string) => { - let isValidUsername: boolean = usernameRegex.test(username); - setForm({ - ...form, - username, - isValidUsername, - }); - }; - /* - * Handles changes to the password field value and verifies the input by updating state and running a validation function. - */ - const handlePasswordUpdate = (password: string) => { - let isValidPassword: boolean = passwordRegex.test(password); - let passwordsMatch: boolean = form.password === form.confirm; - setForm({ - ...form, - password, - isValidPassword, - passwordsMatch, - }); - }; - - /* - * Handles changes to the confirm password field value and verifies the input by updating state and running a validation function. - */ - const handleConfirmUpdate = (confirm: string) => { - let passwordsMatch: boolean = form.password === confirm; - setForm({ - ...form, - confirm, - passwordsMatch, - }); - }; - - /** - * Handles changes to the terms and conditions accepted boolean. - * @param tcAccepted the boolean to set the terms and conditions value to - */ - const handleTcUpdate = (tcAccepted: boolean) => { - setForm({ - ...form, - tcAccepted, - }); - }; - - /** - * Handles a click on the "next" arrow button by sending an API request to the backend and displaying the appropriate response. - */ - const handleRegister = async () => { - if (!form.attemptedSubmit) { - setForm({ - ...form, - attemptedSubmit: true, - }); - } - try { - if ( - form.isValidFname && - form.isValidLname && - form.isValidUsername && - form.isValidPassword && - form.passwordsMatch - ) { - if (form.tcAccepted) { - let registerResponse = await fetch(REGISTER_ENDPOINT, { - method: 'POST', - body: JSON.stringify({ - first_name: form.fname, - last_name: form.lname, - email: form.email, - username: form.username, - password: form.password, - }), - }); - let statusCode = registerResponse.status; - let data = await registerResponse.json(); - if (statusCode === 201) { - let sendOtpResponse = await trackPromise( - fetch(SEND_OTP_ENDPOINT, { - method: 'POST', - body: JSON.stringify({ - username: form.username, - email: form.email, - }), - }), - ); - let otpStatusCode = sendOtpResponse.status; - if (otpStatusCode === 200) { - const userId: string = data.UserID; - navigation.navigate('Verification', { - username: form.username, - email: form.email, - userId: userId, - }); - } - } else if (statusCode === 409) { - Alert.alert('Registration failed πŸ˜”', `${data}`); - } else { - Alert.alert( - 'Something went wrong! 😭', - "Would you believe me if I told you that I don't know what happened?", - ); - } - } else { - Alert.alert( - 'Terms and conditions', - 'You must first agree to the terms and conditions.', - ); - } - } else { - setForm({...form, attemptedSubmit: false}); - setTimeout(() => setForm({...form, attemptedSubmit: true})); - } - } catch (error) { - Alert.alert( - 'Registration failed πŸ˜“', - 'Please double-check your network connection and retry.', - ); - return { - name: 'Registration error', - description: error, - }; - } - }; - - const Footer = () => ( - - navigation.navigate('Login')} - /> - - - - - ); - - /** - * An activity indicator to indicate that the app is working during the send_otp request. - */ - const LoadingIndicator = () => { - const {promiseInProgress} = usePromiseTracker(); - return promiseInProgress ? ( - - ) : ( - <> - ); - }; - - return ( - - - - - - Sign up. - - handleFocusChange('lname')} - blurOnSubmit={false} - valid={form.isValidFname} - invalidWarning="First name cannot be empty." - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - handleFocusChange('email')} - blurOnSubmit={false} - ref={lnameRef} - valid={form.isValidLname} - invalidWarning="Last name cannot be empty." - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - handleFocusChange('username')} - blurOnSubmit={false} - ref={emailRef} - valid={form.isValidEmail} - invalidWarning={'Please enter a valid email address.'} - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - handleFocusChange('password')} - blurOnSubmit={false} - ref={usernameRef} - valid={form.isValidUsername} - invalidWarning={ - 'Username must beΒ at least 6 characters and contain only alphanumerics.' - } - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - handleFocusChange('confirm')} - blurOnSubmit={false} - secureTextEntry - ref={passwordRef} - valid={form.isValidPassword} - invalidWarning={ - 'Password must be at least 8 characters & contain at least one of a-z, A-Z, 0-9, and a special character.' - } - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - - - - -