diff options
| author | Ivan Chen <ivan@tagg.id> | 2021-05-07 20:15:18 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-05-07 20:15:18 -0400 |
| commit | 1c3f2c5712ca0c865f5933b3e6b1aff7604e9f35 (patch) | |
| tree | d912c10dc5efe7ce2fa99423b7c707a44adb39de /src/screens/onboarding/legacy | |
| parent | eb1a01196ae6a999ac73e146cf5286fddc0454e3 (diff) | |
| parent | 525678fde244e8e9d3ea373ee8fb93c26ea92be8 (diff) | |
Merge pull request #405 from grusuTagg/tma823-onboarding-revamp
[TMA-823] Onboarding Revamp
Diffstat (limited to 'src/screens/onboarding/legacy')
| -rw-r--r-- | src/screens/onboarding/legacy/OnboardingStepOne.tsx | 273 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/OnboardingStepTwo.tsx | 381 |
2 files changed, 654 insertions, 0 deletions
diff --git a/src/screens/onboarding/legacy/OnboardingStepOne.tsx b/src/screens/onboarding/legacy/OnboardingStepOne.tsx new file mode 100644 index 00000000..b25d41fd --- /dev/null +++ b/src/screens/onboarding/legacy/OnboardingStepOne.tsx @@ -0,0 +1,273 @@ +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo, useRef, useState} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { + ArrowButton, + Background, + RegistrationWizard, + TaggInput, +} from '../../../components'; +import {nameRegex, phoneRegex} from '../../../constants'; +import { + ERROR_NEXT_PAGE, + ERROR_PHONE_IN_USE, + ERROR_TWILIO_SERVER_ERROR, +} from '../../../constants/strings'; +import {OnboardingStackParams} from '../../../routes'; +import {sendOtpStatusCode} from '../../../services'; +import {BackgroundGradientType} from '../../../types'; +import {SCREEN_HEIGHT} from '../../../utils'; + +type OnboardingStepOneNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'OnboardingStepOne' +>; +interface OnboardingStepOneProps { + navigation: OnboardingStepOneNavigationProp; +} + +const OnboardingStepOne: React.FC<OnboardingStepOneProps> = ({navigation}) => { + const lnameRef = useRef(); + const emailRef = useRef(); + const phoneRef = useRef(); + + 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 'phone': + const phoneField: any = phoneRef.current; + phoneField.focus(); + break; + default: + return; + } + }; + + const [form, setForm] = useState({ + fname: '', + lname: '', + phone: '', + isValidFname: false, + isValidLname: false, + isValidPhone: false, + attemptedSubmit: false, + token: '', + }); + + const handleFnameUpdate = (fname: string) => { + fname = fname.trim(); + let isValidFname: boolean = nameRegex.test(fname); + setForm({ + ...form, + fname, + isValidFname, + }); + }; + + const handleLnameUpdate = (lname: string) => { + lname = lname.trim(); + let isValidLname: boolean = nameRegex.test(lname); + setForm({ + ...form, + lname, + isValidLname, + }); + }; + + const handlePhoneUpdate = (phone: string) => { + phone = phone.trim(); + let isValidPhone: boolean = phoneRegex.test(phone); + setForm({ + ...form, + phone, + isValidPhone, + }); + }; + + const goNext = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + const {isValidFname, isValidLname, isValidPhone} = form; + if (isValidFname && isValidLname && isValidPhone) { + const {phone} = form; + const code = await sendOtpStatusCode(phone); + if (code) { + switch (code) { + case 200: + const {fname, lname} = form; + navigation.navigate('PhoneVerification', { + firstName: fname, + lastName: lname, + phone, + }); + break; + case 409: + Alert.alert(ERROR_PHONE_IN_USE); + break; + default: + Alert.alert(ERROR_TWILIO_SERVER_ERROR); + } + } + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert(ERROR_NEXT_PAGE); + return { + name: 'Navigation error', + description: error, + }; + } + }; + + const footer = useMemo( + () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => navigation.navigate('Login')} + /> + <TouchableOpacity onPress={goNext}> + <ArrowButton + direction="forward" + disabled={ + !(form.isValidFname && form.isValidLname && form.isValidPhone) + } + onPress={goNext} + /> + </TouchableOpacity> + </View> + ), + [ + form.fname, + form.lname, + form.phone, + form.isValidFname, + form.isValidLname, + form.isValidPhone, + ], + ); + + return ( + <Background + style={styles.container} + gradientType={BackgroundGradientType.Light}> + <StatusBar barStyle="light-content" /> + <RegistrationWizard style={styles.wizard} step="one" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <View> + <Text style={styles.formHeader}>ENTER NAME</Text> + </View> + <TaggInput + accessibilityHint="Enter your first name." + accessibilityLabel="First name input field." + placeholder="First Name" + autoCompleteType="name" + textContentType="name" + returnKeyType="next" + onChangeText={handleFnameUpdate} + onSubmitEditing={() => handleFocusChange('lname')} + blurOnSubmit={false} + valid={form.isValidFname} + invalidWarning="Please enter a valid first name." + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + accessibilityHint="Enter your last name." + accessibilityLabel="Last name input field." + placeholder="Last Name" + autoCompleteType="name" + textContentType="name" + returnKeyType="next" + onChangeText={handleLnameUpdate} + blurOnSubmit={false} + ref={lnameRef} + valid={form.isValidLname} + invalidWarning="Please enter a valid last name." + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + maxLength={10} // currently only support US phone numbers + accessibilityHint="Enter your phone number." + accessibilityLabel="Phone number input field." + placeholder="Phone Number" + autoCompleteType="tel" + textContentType="telephoneNumber" + autoCapitalize="none" + keyboardType="number-pad" + onChangeText={handlePhoneUpdate} + blurOnSubmit={false} + ref={phoneRef} + valid={form.isValidPhone} + invalidWarning={'Please enter a valid 10 digit number.'} + attemptedSubmit={form.attemptedSubmit} + width={280} + onSubmitEditing={goNext} + /> + </KeyboardAvoidingView> + {footer} + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + wizard: { + position: 'absolute', + top: SCREEN_HEIGHT * 0.1, + }, + formHeader: { + color: '#fff', + fontSize: 30, + fontWeight: '600', + marginBottom: '16%', + }, + load: { + top: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); + +export default OnboardingStepOne;
\ No newline at end of file diff --git a/src/screens/onboarding/legacy/OnboardingStepTwo.tsx b/src/screens/onboarding/legacy/OnboardingStepTwo.tsx new file mode 100644 index 00000000..40130263 --- /dev/null +++ b/src/screens/onboarding/legacy/OnboardingStepTwo.tsx @@ -0,0 +1,381 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo, useRef, useState} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { + ArrowButton, + Background, + LoadingIndicator, + RegistrationWizard, + TaggInput, + TermsConditions, +} from '../../../components'; +import {emailRegex, passwordRegex, usernameRegex} from '../../../constants'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_REGISTRATION, + ERROR_SOMETHING_WENT_WRONG_REFRESH, +} from '../../../constants/strings'; +import {OnboardingStackParams} from '../../../routes'; +import {sendRegister} from '../../../services'; +import {BackgroundGradientType} from '../../../types'; +import {SCREEN_HEIGHT} from '../../../utils'; + +type OnboardingStepTwoRouteProp = RouteProp< + OnboardingStackParams, + 'OnboardingStepTwo' +>; +type OnboardingStepTwoNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'OnboardingStepTwo' +>; +interface OnboardingStepTwoProps { + route: OnboardingStepTwoRouteProp; + navigation: OnboardingStepTwoNavigationProp; +} + +const OnboardingStepTwo: React.FC<OnboardingStepTwoProps> = ({ + route, + navigation, +}) => { + const emailRef = useRef(); + const usernameRef = useRef(); + const passwordRef = useRef(); + const confirmRef = useRef(); + + const handleFocusChange = (field: string): void => { + switch (field) { + 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({ + email: '', + username: '', + password: '', + confirm: '', + isValidEmail: false, + isValidUsername: false, + isValidPassword: false, + passwordsMatch: false, + tcAccepted: false, + attemptedSubmit: false, + }); + + const handleEmailUpdate = (email: string) => { + email = email.trim(); + let isValidEmail: boolean = emailRegex.test(email); + setForm({ + ...form, + email, + isValidEmail, + }); + }; + + const handleUsernameUpdate = (username: string) => { + let isValidUsername: boolean = usernameRegex.test(username); + setForm({ + ...form, + username, + isValidUsername, + }); + }; + + const handlePasswordUpdate = (password: string) => { + let isValidPassword: boolean = passwordRegex.test(password); + let passwordsMatch: boolean = form.password === form.confirm; + setForm({ + ...form, + password, + isValidPassword, + passwordsMatch, + }); + }; + + const handleConfirmUpdate = (confirm: string) => { + let passwordsMatch: boolean = form.password === confirm; + setForm({ + ...form, + confirm, + passwordsMatch, + }); + }; + + const handleTcUpdate = (tcAccepted: boolean) => { + setForm({ + ...form, + tcAccepted, + }); + }; + + const handleRegister = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + const { + isValidEmail, + isValidUsername, + isValidPassword, + passwordsMatch, + tcAccepted, + } = form; + try { + if ( + isValidEmail && + isValidUsername && + isValidPassword && + passwordsMatch + ) { + if (tcAccepted) { + const {email, username, password} = form; + const {firstName, lastName, phone} = route.params; + const response = await sendRegister( + firstName, + lastName, + phone, + email, + username, + password, + ); + if (response) { + const data = await response.json(); + const {token, UserID} = data; + switch (response.status) { + case 201: + await AsyncStorage.setItem('token', token); + navigation.navigate('OnboardingStepThree', { + userId: UserID, + username: username, + }); + break; + case 400: + Alert.alert(ERROR_REGISTRATION(data.toLowerCase())); + break; + default: + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + break; + } + } else { + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + } + } 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(ERROR_REGISTRATION(ERROR_DOUBLE_CHECK_CONNECTION)); + return { + name: 'Registration error', + description: error, + }; + } + }; + + const footer = useMemo( + () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => + navigation.navigate('PhoneVerification', {...route.params}) + } + /> + <TouchableOpacity onPress={handleRegister}> + <ArrowButton + direction="forward" + disabled={ + !( + form.isValidUsername && + form.isValidPassword && + form.passwordsMatch && + form.tcAccepted + ) + } + onPress={handleRegister} + /> + </TouchableOpacity> + </View> + ), + [ + form.email, + form.username, + form.password, + form.confirm, + form.isValidEmail, + form.isValidUsername, + form.isValidPassword, + form.passwordsMatch, + form.tcAccepted, + ], + ); + + return ( + <Background + style={styles.container} + gradientType={BackgroundGradientType.Light}> + <StatusBar barStyle="light-content" /> + <RegistrationWizard style={styles.wizard} step="two" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <View> + <Text style={styles.formHeader}>SIGN UP</Text> + </View> + <TaggInput + accessibilityHint="Enter a username." + accessibilityLabel="Username input field." + placeholder="Username" + autoCompleteType="username" + textContentType="username" + autoCapitalize="none" + returnKeyType="next" + onChangeText={handleUsernameUpdate} + onSubmitEditing={() => 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} + /> + <TaggInput + accessibilityHint="Enter your email." + accessibilityLabel="Email input field." + placeholder="School Email" + autoCompleteType="email" + textContentType="emailAddress" + autoCapitalize="none" + returnKeyType="next" + keyboardType="email-address" + onChangeText={handleEmailUpdate} + blurOnSubmit={false} + ref={emailRef} + valid={form.isValidEmail} + invalidWarning={'Please enter a valid email address.'} + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + accessibilityHint="Enter a password." + accessibilityLabel="Password input field." + placeholder="Password" + autoCompleteType="password" + textContentType="oneTimeCode" + returnKeyType="next" + onChangeText={handlePasswordUpdate} + onSubmitEditing={() => 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} + /> + <TaggInput + accessibilityHint={'Re-enter your password.'} + accessibilityLabel={'Password confirmation input field.'} + placeholder={'Confirm Password'} + autoCompleteType="password" + textContentType="oneTimeCode" + returnKeyType={form.tcAccepted ? 'go' : 'default'} + onChangeText={handleConfirmUpdate} + onSubmitEditing={handleRegister} + secureTextEntry + ref={confirmRef} + valid={form.passwordsMatch} + invalidWarning={'Passwords must match.'} + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <LoadingIndicator /> + <TermsConditions + style={styles.tc} + accepted={form.tcAccepted} + onChange={handleTcUpdate} + /> + </KeyboardAvoidingView> + {footer} + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + wizard: { + position: 'absolute', + top: SCREEN_HEIGHT * 0.1, + }, + formHeader: { + color: '#fff', + fontSize: 30, + fontWeight: '600', + marginBottom: '16%', + }, + tc: { + marginVertical: '5%', + }, + load: { + top: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); + +export default OnboardingStepTwo;
\ No newline at end of file |
