aboutsummaryrefslogtreecommitdiff
path: root/src/screens/onboarding/legacy
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/onboarding/legacy')
-rw-r--r--src/screens/onboarding/legacy/AddWaitlistUserScreen.tsx237
-rw-r--r--src/screens/onboarding/legacy/Checkpoint.tsx142
-rw-r--r--src/screens/onboarding/legacy/ProfileOnboarding.tsx575
-rw-r--r--src/screens/onboarding/legacy/RegistrationOne.tsx211
-rw-r--r--src/screens/onboarding/legacy/RegistrationThree.tsx363
-rw-r--r--src/screens/onboarding/legacy/RegistrationTwo.tsx275
-rw-r--r--src/screens/onboarding/legacy/SocialMedia.tsx161
-rw-r--r--src/screens/onboarding/legacy/TaggPopup.tsx143
-rw-r--r--src/screens/onboarding/legacy/WaitlistSuccessScreen.tsx156
9 files changed, 2263 insertions, 0 deletions
diff --git a/src/screens/onboarding/legacy/AddWaitlistUserScreen.tsx b/src/screens/onboarding/legacy/AddWaitlistUserScreen.tsx
new file mode 100644
index 00000000..489c30f1
--- /dev/null
+++ b/src/screens/onboarding/legacy/AddWaitlistUserScreen.tsx
@@ -0,0 +1,237 @@
+import {StackNavigationProp} from '@react-navigation/stack';
+import * as React from 'react';
+import {
+ KeyboardAvoidingView,
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {
+ ArrowButton,
+ Background,
+ LoadingIndicator,
+ TaggInput,
+} from '../../components';
+import {nameRegex, phoneRegex} from '../../constants';
+import {OnboardingStackParams} from '../../routes';
+import {adduserToWaitlist} from '../../services';
+import {BackgroundGradientType} from '../../types';
+import {SCREEN_HEIGHT} from '../../utils';
+
+type AddWaitlistUserScreenProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'AddWaitlistUser'
+>;
+
+interface AddWaitlistUserScreenProps {
+ navigation: AddWaitlistUserScreenProp;
+}
+
+const AddWaitlistUserScreen: React.FC<AddWaitlistUserScreenProps> = ({
+ navigation,
+}) => {
+ const phoneRef = React.useRef();
+ const lnameRef = React.useRef();
+
+ const [form, setForm] = React.useState({
+ phone_number: {value: '', isValid: false},
+ first_name: {value: '', isValid: false},
+ last_name: {value: '', isValid: false},
+ attemptedSubmit: false,
+ });
+
+ //Handlers
+ const handleFocusChange = (field: string): void => {
+ switch (field) {
+ case 'last_name':
+ const lnameField: any = lnameRef.current;
+ lnameField.focus();
+ break;
+ case 'phone_number':
+ const phoneField: any = phoneRef.current;
+ phoneField.focus();
+ break;
+ default:
+ return;
+ }
+ };
+
+ const validate = (value: string, type: string) => {
+ let isValid: boolean = false;
+ switch (type) {
+ case 'phone_number':
+ isValid = phoneRegex.test(value);
+ break;
+ default:
+ isValid = nameRegex.test(value);
+ break;
+ }
+ return isValid;
+ };
+
+ const handleUpdate = (value: string, type: string) => {
+ value = value.trim();
+ const isValid = validate(value, type);
+ setForm({
+ ...form,
+ [type]: {value, isValid},
+ });
+ };
+
+ const handleAddUser = async () => {
+ if (!form.attemptedSubmit) {
+ setForm({
+ ...form,
+ attemptedSubmit: true,
+ });
+ }
+ try {
+ const {phone_number, first_name, last_name} = form;
+ if (phone_number.isValid && first_name.isValid && last_name.isValid) {
+ const success = await adduserToWaitlist(
+ phone_number.value,
+ first_name.value,
+ last_name.value,
+ );
+ if (success) {
+ navigation.navigate('WaitlistSuccess');
+ }
+ } else {
+ setForm({...form, attemptedSubmit: false});
+ setTimeout(() => setForm({...form, attemptedSubmit: true}));
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ //Components
+ const Footer = () => (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="backward"
+ onPress={() => navigation.navigate('InvitationCodeVerification')}
+ />
+ </View>
+ );
+
+ const {phone_number, first_name, last_name} = form;
+
+ return (
+ <Background
+ style={styles.container}
+ gradientType={BackgroundGradientType.Light}>
+ <StatusBar barStyle="light-content" />
+ <KeyboardAvoidingView
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
+ style={styles.container}>
+ <View>
+ <Text style={styles.formHeader}>JOIN WAITLIST</Text>
+ </View>
+ <TaggInput
+ accessibilityHint="Enter your first name."
+ accessibilityLabel="First name input field."
+ placeholder="First Name"
+ autoCompleteType="name"
+ textContentType="name"
+ returnKeyType="next"
+ onChangeText={(text) => handleUpdate(text, 'first_name')}
+ onSubmitEditing={() => handleFocusChange('first_name')}
+ blurOnSubmit={false}
+ valid={first_name.isValid}
+ 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={(text) => handleUpdate(text, 'last_name')}
+ blurOnSubmit={false}
+ ref={lnameRef}
+ valid={last_name.isValid}
+ invalidWarning="Please enter a valid last name."
+ onSubmitEditing={() => handleFocusChange('phone_number')}
+ attemptedSubmit={form.attemptedSubmit}
+ width={280}
+ />
+ <TaggInput
+ maxLength={12}
+ accessibilityHint="Enter your phone number."
+ accessibilityLabel="Phone number input field."
+ placeholder="Phone Number"
+ autoCompleteType="tel"
+ textContentType="telephoneNumber"
+ autoCapitalize="none"
+ returnKeyType="next"
+ keyboardType="phone-pad"
+ onChangeText={(text) => handleUpdate(text, 'phone_number')}
+ blurOnSubmit={false}
+ ref={phoneRef}
+ valid={phone_number.isValid}
+ invalidWarning="Please enter a valid 10 digit number."
+ onSubmitEditing={handleAddUser}
+ attemptedSubmit={form.attemptedSubmit}
+ width={280}
+ />
+ <TouchableOpacity onPress={handleAddUser} style={styles.finalAction}>
+ <Text style={styles.finalActionLabel}>Submit</Text>
+ </TouchableOpacity>
+ <LoadingIndicator />
+ </KeyboardAvoidingView>
+ <Footer />
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ finalAction: {
+ backgroundColor: 'white',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 150,
+ height: 40,
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#fff',
+ marginVertical: SCREEN_HEIGHT / 20,
+ },
+ finalActionLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: 'black',
+ },
+ formHeader: {
+ color: '#fff',
+ fontSize: 30,
+ fontWeight: '600',
+ marginBottom: '16%',
+ },
+ footer: {
+ width: '100%',
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ ...Platform.select({
+ ios: {
+ bottom: '20%',
+ },
+ android: {
+ bottom: '10%',
+ },
+ }),
+ },
+});
+
+export default AddWaitlistUserScreen;
diff --git a/src/screens/onboarding/legacy/Checkpoint.tsx b/src/screens/onboarding/legacy/Checkpoint.tsx
new file mode 100644
index 00000000..93ff0b7f
--- /dev/null
+++ b/src/screens/onboarding/legacy/Checkpoint.tsx
@@ -0,0 +1,142 @@
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import React from 'react';
+import {
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {Background, RegistrationWizard} from '../../../components';
+import {OnboardingStackParams} from '../../../routes';
+import {BackgroundGradientType} from '../../../types';
+
+type CheckpointRouteProp = RouteProp<OnboardingStackParams, 'Checkpoint'>;
+type CheckpointNavigationProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'Checkpoint'
+>;
+interface CheckpointProps {
+ route: CheckpointRouteProp;
+ navigation: CheckpointNavigationProp;
+}
+/**
+ * Checkpoint to ask user if profile setup should be done
+ * @param navigation react-navigation navigation object
+ */
+const Checkpoint: React.FC<CheckpointProps> = ({route, navigation}) => {
+ const {userId, username} = route.params;
+
+ const handleSkip = () => {
+ navigation.navigate('SocialMedia', {
+ userId: userId,
+ username: username,
+ });
+ };
+
+ const handleProceed = () => {
+ navigation.navigate('ProfileOnboarding', {
+ userId: userId,
+ username: username,
+ });
+ };
+
+ return (
+ <Background
+ style={styles.container}
+ gradientType={BackgroundGradientType.Light}>
+ <StatusBar barStyle="light-content" />
+ <RegistrationWizard style={styles.wizard} step="six" />
+ <View style={styles.textContainer}>
+ <Text style={styles.header}>You are registered!</Text>
+ <Text style={styles.subtext}>
+ We're almost there. Would you like to setup your profile now?
+ </Text>
+ <View style={styles.buttonContainer}>
+ <TouchableOpacity onPress={handleSkip} style={styles.skipButton}>
+ <Text style={styles.skipButtonLabel}>Do it later</Text>
+ </TouchableOpacity>
+ <TouchableOpacity
+ onPress={handleProceed}
+ style={styles.proceedButton}>
+ <Text style={styles.proceedButtonLabel}>Let's do it!</Text>
+ </TouchableOpacity>
+ </View>
+ </View>
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ },
+ textContainer: {
+ marginTop: '65%',
+ },
+
+ buttonContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-evenly',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ header: {
+ color: '#fff',
+ fontSize: 22,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '4%',
+ marginHorizontal: '10%',
+ },
+ subtext: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '16%',
+ marginHorizontal: '10%',
+ },
+ proceedButton: {
+ backgroundColor: '#8F01FF',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 150,
+ height: 40,
+ borderRadius: 5,
+ marginTop: '5%',
+ },
+ proceedButtonLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#fff',
+ },
+ skipButton: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 150,
+ height: 40,
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#ddd',
+ marginTop: '5%',
+ },
+ skipButtonLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#ddd',
+ },
+});
+
+export default Checkpoint;
diff --git a/src/screens/onboarding/legacy/ProfileOnboarding.tsx b/src/screens/onboarding/legacy/ProfileOnboarding.tsx
new file mode 100644
index 00000000..e994c1e6
--- /dev/null
+++ b/src/screens/onboarding/legacy/ProfileOnboarding.tsx
@@ -0,0 +1,575 @@
+import AsyncStorage from '@react-native-community/async-storage';
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import moment from 'moment';
+import React, {useMemo} from 'react';
+import {
+ Alert,
+ Image,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import ImagePicker from 'react-native-image-crop-picker';
+import Animated from 'react-native-reanimated';
+import {
+ Background,
+ BirthDatePicker,
+ TaggBigInput,
+ TaggDropDown,
+ TaggInput,
+} from '../../../components';
+import {
+ bioRegex,
+ CLASS_YEAR_LIST,
+ EDIT_PROFILE_ENDPOINT,
+ genderRegex,
+ TAGG_PURPLE,
+ websiteRegex,
+} from '../../../constants';
+import {
+ ERROR_DOUBLE_CHECK_CONNECTION,
+ ERROR_PROFILE_CREATION_SHORT,
+ ERROR_SELECT_CLASS_YEAR,
+ ERROR_SOMETHING_WENT_WRONG_REFRESH,
+ ERROR_UPLOAD_LARGE_PROFILE_PIC,
+ ERROR_UPLOAD_SMALL_PROFILE_PIC,
+} from '../../../constants/strings';
+import {OnboardingStackParams} from '../../../routes/onboarding';
+import {BackgroundGradientType} from '../../../types';
+import {SCREEN_WIDTH} from '../../../utils';
+
+type ProfileOnboardingScreenRouteProp = RouteProp<
+ OnboardingStackParams,
+ 'ProfileOnboarding'
+>;
+type ProfileOnboardingScreenNavigationProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'ProfileOnboarding'
+>;
+interface ProfileOnboardingProps {
+ route: ProfileOnboardingScreenRouteProp;
+ navigation: ProfileOnboardingScreenNavigationProp;
+}
+
+/**
+ * Create profile screen for onboarding.
+ * @param navigation react-navigation navigation object
+ */
+
+const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
+ route,
+ navigation,
+}) => {
+ const {userId, username} = route.params;
+ let emptyDate: string | undefined;
+ const [form, setForm] = React.useState({
+ largePic: '',
+ smallPic: '',
+ website: '',
+ bio: '',
+ birthdate: emptyDate,
+ gender: '',
+ isValidWebsite: true,
+ isValidBio: true,
+ isValidGender: true,
+ attemptedSubmit: false,
+ token: '',
+ classYear: -1,
+ });
+ const [customGender, setCustomGender] = React.useState(Boolean);
+
+ // refs for changing focus
+ const bioRef = React.useRef();
+ const birthdateRef = React.useRef();
+ const genderRef = React.useRef();
+ const customGenderRef = React.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 'bio':
+ const bioField: any = bioRef.current;
+ bioField.focus();
+ break;
+ case 'birthdate':
+ const birthdateField: any = birthdateRef.current;
+ birthdateField.focus();
+ break;
+ case 'gender':
+ const genderField: any = genderRef.current;
+ genderField.focus();
+ break;
+ case 'customGender':
+ const customGenderField: any = customGenderRef.current;
+ customGenderField.focus();
+ break;
+ default:
+ return;
+ }
+ };
+
+ var classYearList: Array<any> = [];
+
+ CLASS_YEAR_LIST.map((value) => {
+ classYearList.push({label: value, value: value});
+ });
+
+ /**
+ * Profile screen "Add header image" button
+ */
+ const LargeProfilePic = () => (
+ <TouchableOpacity
+ accessible={true}
+ accessibilityLabel="ADD HEADER IMAGE"
+ onPress={goToGalleryLargePic}
+ style={styles.largeProfileUploader}>
+ {form.largePic ? (
+ <Image source={{uri: form.largePic}} style={styles.largeProfilePic} />
+ ) : (
+ <Text style={styles.largeProfileText}>ADD HEADER IMAGE</Text>
+ )}
+ </TouchableOpacity>
+ );
+
+ /**
+ * Profile screen "Add profile picture" button
+ */
+ const SmallProfilePic = () => (
+ <TouchableOpacity
+ accessible={true}
+ accessibilityLabel="ADD PROFILE PICTURE"
+ onPress={goToGallerySmallPic}
+ style={styles.smallProfileUploader}>
+ {form.smallPic ? (
+ <Image source={{uri: form.smallPic}} style={styles.smallProfilePic} />
+ ) : (
+ <Text style={styles.smallProfileText}>ADD PROFILE PICTURE</Text>
+ )}
+ </TouchableOpacity>
+ );
+
+ const goToGalleryLargePic = () => {
+ ImagePicker.openPicker({
+ smartAlbums: [
+ 'Favorites',
+ 'RecentlyAdded',
+ 'SelfPortraits',
+ 'Screenshots',
+ 'UserLibrary',
+ ],
+ width: 580,
+ height: 580,
+ cropping: true,
+ cropperToolbarTitle: 'Select Header',
+ mediaType: 'photo',
+ }).then((picture) => {
+ if ('path' in picture) {
+ setForm({
+ ...form,
+ largePic: picture.path,
+ });
+ }
+ });
+ };
+
+ const goToGallerySmallPic = () => {
+ ImagePicker.openPicker({
+ smartAlbums: [
+ 'Favorites',
+ 'RecentlyAdded',
+ 'SelfPortraits',
+ 'Screenshots',
+ 'UserLibrary',
+ ],
+ width: 580,
+ height: 580,
+ cropping: true,
+ cropperToolbarTitle: 'Select Profile Picture',
+ mediaType: 'photo',
+ cropperCircleOverlay: true,
+ }).then((picture) => {
+ if ('path' in picture) {
+ setForm({
+ ...form,
+ smallPic: picture.path,
+ });
+ }
+ });
+ };
+
+ /*
+ * Handles changes to the website field value and verifies the input by updating state and running a validation function.
+ */
+ const handleWebsiteUpdate = (website: string) => {
+ website = website.trim();
+ let isValidWebsite: boolean = websiteRegex.test(website);
+ setForm({
+ ...form,
+ website,
+ isValidWebsite,
+ });
+ };
+
+ /*
+ * Handles changes to the bio field value and verifies the input by updating state and running a validation function.
+ */
+ const handleBioUpdate = (bio: string) => {
+ let isValidBio: boolean = bioRegex.test(bio);
+ setForm({
+ ...form,
+ bio,
+ isValidBio,
+ });
+ };
+
+ const handleGenderUpdate = (gender: string) => {
+ if (gender === 'custom') {
+ setCustomGender(true);
+ } else {
+ setCustomGender(false);
+ let isValidGender: boolean = true;
+ setForm({
+ ...form,
+ gender,
+ isValidGender,
+ });
+ }
+ };
+
+ const handleClassYearUpdate = (value: string) => {
+ const classYear = parseInt(value, 10);
+ setForm({
+ ...form,
+ classYear,
+ });
+ };
+
+ const handleCustomGenderUpdate = (gender: string) => {
+ let isValidGender: boolean = genderRegex.test(gender);
+ gender = gender.replace(' ', '-');
+ setForm({
+ ...form,
+ gender,
+ isValidGender,
+ });
+ };
+
+ const handleBirthdateUpdate = (birthdate: Date) => {
+ setForm({
+ ...form,
+ birthdate: birthdate && moment(birthdate).format('YYYY-MM-DD'),
+ });
+ };
+
+ const handleSubmit = async () => {
+ if (!form.largePic) {
+ Alert.alert(ERROR_UPLOAD_LARGE_PROFILE_PIC);
+ return;
+ }
+ if (!form.smallPic) {
+ Alert.alert(ERROR_UPLOAD_SMALL_PROFILE_PIC);
+ return;
+ }
+ if (form.classYear === -1) {
+ Alert.alert(ERROR_SELECT_CLASS_YEAR);
+ return;
+ }
+ if (!form.attemptedSubmit) {
+ setForm({
+ ...form,
+ attemptedSubmit: true,
+ });
+ }
+ let invalidFields: boolean = false;
+ const request = new FormData();
+ if (form.largePic) {
+ request.append('largeProfilePicture', {
+ uri: form.largePic,
+ name: 'large_profile_pic.jpg',
+ type: 'image/jpg',
+ });
+ }
+ if (form.smallPic) {
+ request.append('smallProfilePicture', {
+ uri: form.smallPic,
+ name: 'small_profile_pic.jpg',
+ type: 'image/jpg',
+ });
+ }
+ if (form.website) {
+ if (form.isValidWebsite) {
+ request.append('website', form.website);
+ } else {
+ setForm({...form, attemptedSubmit: false});
+ setTimeout(() => setForm({...form, attemptedSubmit: true}));
+ invalidFields = true;
+ }
+ }
+
+ if (form.bio) {
+ if (form.isValidBio) {
+ request.append('biography', form.bio);
+ } else {
+ setForm({...form, attemptedSubmit: false});
+ setTimeout(() => setForm({...form, attemptedSubmit: true}));
+ invalidFields = true;
+ }
+ }
+
+ if (form.birthdate) {
+ request.append('birthday', form.birthdate);
+ }
+
+ if (customGender) {
+ if (form.isValidGender) {
+ request.append('gender', form.gender);
+ } else {
+ setForm({...form, attemptedSubmit: false});
+ setTimeout(() => setForm({...form, attemptedSubmit: true}));
+ invalidFields = true;
+ }
+ } else {
+ if (form.isValidGender) {
+ request.append('gender', form.gender);
+ }
+ }
+
+ if (form.classYear !== -1) {
+ request.append('university_class', form.classYear);
+ }
+
+ if (invalidFields) {
+ return;
+ }
+
+ const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`;
+ try {
+ const token = await AsyncStorage.getItem('token');
+ let response = await fetch(endpoint, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: 'Token ' + token,
+ },
+ body: request,
+ });
+ let statusCode = response.status;
+ let data = await response.json();
+ if (statusCode === 200) {
+ navigation.navigate('SocialMedia', {
+ userId: userId,
+ username: username,
+ });
+ } else if (statusCode === 400) {
+ Alert.alert(
+ 'Profile update failed. ๐Ÿ˜”',
+ data.error || 'Something went wrong! ๐Ÿ˜ญ',
+ );
+ } else {
+ Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH);
+ }
+ } catch (error) {
+ Alert.alert(ERROR_PROFILE_CREATION_SHORT, ERROR_DOUBLE_CHECK_CONNECTION);
+ return {
+ name: 'Profile creation error',
+ description: error,
+ };
+ }
+ };
+
+ const profilePics = useMemo(() => {
+ return (
+ <View style={styles.profile}>
+ <LargeProfilePic />
+ <SmallProfilePic />
+ </View>
+ );
+ }, [form.largePic, form.smallPic]);
+
+ return (
+ <Animated.ScrollView bounces={false}>
+ <Background
+ centered
+ gradientType={BackgroundGradientType.Light}
+ style={styles.container}>
+ <StatusBar barStyle="light-content" />
+ {profilePics}
+ <View style={styles.contentContainer}>
+ <TaggInput
+ accessibilityHint="Enter a website."
+ accessibilityLabel="Website input field."
+ placeholder="Website"
+ autoCompleteType="off"
+ textContentType="URL"
+ autoCapitalize="none"
+ returnKeyType="next"
+ onChangeText={handleWebsiteUpdate}
+ onSubmitEditing={() => handleFocusChange('bio')}
+ blurOnSubmit={false}
+ valid={form.isValidWebsite}
+ attemptedSubmit={form.attemptedSubmit}
+ invalidWarning={'Website must be a valid link to your website'}
+ width={280}
+ />
+ <TaggBigInput
+ accessibilityHint="Enter a bio."
+ accessibilityLabel="Bio input field."
+ placeholder="Bio"
+ autoCompleteType="off"
+ textContentType="none"
+ autoCapitalize="none"
+ returnKeyType="next"
+ onChangeText={handleBioUpdate}
+ onSubmitEditing={() => handleFocusChange('bio')}
+ blurOnSubmit={false}
+ ref={bioRef}
+ valid={form.isValidBio}
+ attemptedSubmit={form.attemptedSubmit}
+ invalidWarning={
+ 'Bio must be less than 150 characters and must contain valid characters'
+ }
+ width={280}
+ />
+ <BirthDatePicker
+ ref={birthdateRef}
+ handleBDUpdate={handleBirthdateUpdate}
+ width={280}
+ date={form.birthdate}
+ showPresetdate={false}
+ />
+ <TaggDropDown
+ onValueChange={(value: string) => handleClassYearUpdate(value)}
+ items={classYearList}
+ placeholder={{
+ label: 'Class Year',
+ value: null,
+ color: '#ddd',
+ }}
+ />
+ {customGender && (
+ <TaggInput
+ accessibilityHint="Custom"
+ accessibilityLabel="Gender input field."
+ placeholder="Enter your gender"
+ autoCompleteType="off"
+ textContentType="none"
+ autoCapitalize="none"
+ returnKeyType="next"
+ blurOnSubmit={false}
+ ref={customGenderRef}
+ onChangeText={handleCustomGenderUpdate}
+ onSubmitEditing={() => handleSubmit()}
+ valid={form.isValidGender}
+ attemptedSubmit={form.attemptedSubmit}
+ invalidWarning={
+ 'Custom field can only contain letters and hyphens'
+ }
+ width={280}
+ />
+ )}
+ <TaggDropDown
+ onValueChange={(value: string) => handleGenderUpdate(value)}
+ items={[
+ {label: 'Male', value: 'male'},
+ {label: 'Female', value: 'female'},
+ {label: 'Custom', value: 'custom'},
+ ]}
+ placeholder={{
+ label: 'Gender',
+ value: null,
+ color: '#ddd',
+ }}
+ />
+ <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn}>
+ <Text style={styles.submitBtnLabel}>Let's start!</Text>
+ </TouchableOpacity>
+ </View>
+ </Background>
+ </Animated.ScrollView>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ marginBottom: '10%',
+ },
+ profile: {
+ flexDirection: 'row',
+ marginBottom: '5%',
+ },
+ contentContainer: {
+ position: 'relative',
+ width: 280,
+ alignSelf: 'center',
+ },
+ largeProfileUploader: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 15,
+ backgroundColor: '#fff',
+ marginLeft: '13%',
+ marginTop: '10%',
+ height: 230,
+ width: 230,
+ borderRadius: 23,
+ },
+ largeProfileText: {
+ textAlign: 'center',
+ fontSize: 14,
+ fontWeight: 'bold',
+ color: '#863FF9',
+ },
+ largeProfilePic: {
+ height: 230,
+ width: 230,
+ borderRadius: 23,
+ },
+ smallProfileUploader: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 20,
+ backgroundColor: '#E1F0FF',
+ right: '18%',
+ marginTop: '38%',
+ height: 110,
+ width: 110,
+ borderRadius: 55,
+ },
+ smallProfileText: {
+ textAlign: 'center',
+ fontSize: 14,
+ fontWeight: 'bold',
+ color: '#806DF4',
+ },
+ smallProfilePic: {
+ height: 110,
+ width: 110,
+ borderRadius: 55,
+ },
+ submitBtn: {
+ backgroundColor: TAGG_PURPLE,
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: SCREEN_WIDTH / 2.5,
+ height: SCREEN_WIDTH / 10,
+ borderRadius: 5,
+ marginTop: '5%',
+ alignSelf: 'center',
+ marginBottom: SCREEN_WIDTH / 3,
+ },
+ submitBtnLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#fff',
+ },
+});
+
+export default ProfileOnboarding;
diff --git a/src/screens/onboarding/legacy/RegistrationOne.tsx b/src/screens/onboarding/legacy/RegistrationOne.tsx
new file mode 100644
index 00000000..4bcc2720
--- /dev/null
+++ b/src/screens/onboarding/legacy/RegistrationOne.tsx
@@ -0,0 +1,211 @@
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import React, {useRef, useState} from 'react';
+import {
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {trackPromise} from 'react-promise-tracker';
+import {
+ ArrowButton,
+ Background,
+ LoadingIndicator,
+ RegistrationWizard,
+ TaggInput,
+} from '../../../components';
+import {phoneRegex, SEND_OTP_ENDPOINT} from '../../../constants';
+import {
+ ERROR_EMAIL_IN_USE,
+ ERROR_SERVER_DOWN,
+} from '../../../constants/strings';
+import {OnboardingStackParams} from '../../../routes';
+import {BackgroundGradientType, VerificationScreenType} from '../../../types';
+
+type RegistrationScreenOneRouteProp = RouteProp<
+ OnboardingStackParams,
+ 'RegistrationOne'
+>;
+type RegistrationScreenOneNavigationProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'RegistrationOne'
+>;
+interface RegistrationOneProps {
+ route: RegistrationScreenOneRouteProp;
+ navigation: RegistrationScreenOneNavigationProp;
+}
+/**
+ * Registration screen 1 for Email
+ * @param navigation react-navigation navigation object
+ */
+const RegistrationOne: React.FC<RegistrationOneProps> = ({navigation}) => {
+ // // refs for changing focus
+ const phoneRef = useRef();
+
+ // registration form state
+ const [form, setForm] = useState({
+ phone_number: '',
+ isValidPhone: false,
+ attemptedSubmit: false,
+ });
+
+ /*
+ * Handles changes to the email field value and verifies the input by updating state and running a validation function.
+ */
+ const handlePhoneUpdate = (phone_number: string) => {
+ phone_number = phone_number.trim();
+ let isValidPhone: boolean = phoneRegex.test(phone_number);
+ setForm({
+ ...form,
+ phone_number,
+ isValidPhone,
+ });
+ };
+
+ /**
+ * Handles a click on the "next" arrow button by navigating to Verification if email is filled correctly
+ */
+ const goToVerification = async () => {
+ if (!form.attemptedSubmit) {
+ setForm({
+ ...form,
+ attemptedSubmit: true,
+ });
+ }
+ try {
+ if (form.isValidPhone) {
+ let sendOtpResponse = await trackPromise(
+ fetch(SEND_OTP_ENDPOINT, {
+ method: 'POST',
+ body: JSON.stringify({
+ phone_number: '+1' + form.phone_number,
+ }),
+ }),
+ );
+ let otpStatusCode = sendOtpResponse.status;
+ if (otpStatusCode === 200) {
+ navigation.navigate('Verification', {
+ id: form.phone_number,
+ screenType: VerificationScreenType.Phone,
+ });
+ } else if (otpStatusCode === 409) {
+ Alert.alert(ERROR_EMAIL_IN_USE);
+ } else {
+ Alert.alert(ERROR_SERVER_DOWN);
+ }
+ } 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: 'Send OTP error',
+ description: error,
+ };
+ }
+ };
+
+ const Footer = () => (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="backward"
+ onPress={() => navigation.navigate('Login')}
+ />
+ <TouchableOpacity onPress={goToVerification}>
+ <ArrowButton
+ direction="forward"
+ disabled={!form.isValidPhone}
+ onPress={goToVerification}
+ />
+ </TouchableOpacity>
+ </View>
+ );
+
+ 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}>ENTER PHONE NUMBER</Text>
+ </View>
+ <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"
+ returnKeyType="next"
+ 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={goToVerification}
+ />
+ <LoadingIndicator />
+ </KeyboardAvoidingView>
+ <Footer />
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ 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 RegistrationOne;
diff --git a/src/screens/onboarding/legacy/RegistrationThree.tsx b/src/screens/onboarding/legacy/RegistrationThree.tsx
new file mode 100644
index 00000000..1f4266ff
--- /dev/null
+++ b/src/screens/onboarding/legacy/RegistrationThree.tsx
@@ -0,0 +1,363 @@
+/**
+ * Author : Ashm Walia
+ * Purpose : Add a new screen to allow the user to enter first and last name
+ */
+
+import AsyncStorage from '@react-native-community/async-storage';
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import React, {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 {passwordRegex, REGISTER_ENDPOINT, usernameRegex} from '../../constants';
+import {
+ ERROR_DOUBLE_CHECK_CONNECTION,
+ ERROR_REGISTRATION,
+ ERROR_SOMETHING_WENT_WRONG_REFRESH,
+} from '../../constants/strings';
+import {OnboardingStackParams} from '../../routes';
+import {BackgroundGradientType} from '../../types';
+
+type RegistrationScreenThreeRouteProp = RouteProp<
+ OnboardingStackParams,
+ 'RegistrationThree'
+>;
+type RegistrationScreenThreeNavigationProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'RegistrationThree'
+>;
+interface RegistrationThreeProps {
+ route: RegistrationScreenThreeRouteProp;
+ navigation: RegistrationScreenThreeNavigationProp;
+}
+/**
+ * Registration screen 3 for username, password, and terms and conditions
+ * @param navigation react-navigation navigation object
+ */
+const RegistrationThree: React.FC<RegistrationThreeProps> = ({
+ route,
+ navigation,
+}) => {
+ // refs for changing focus
+ const usernameRef = useRef();
+ const passwordRef = useRef();
+ const confirmRef = useRef();
+
+ const registrationName = route.params;
+ const fname: string = registrationName!.firstName;
+ const lname: string = registrationName!.lastName;
+ const phone: string = registrationName!.phone;
+ const email: string = registrationName!.email;
+
+ /**
+ * 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 '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({
+ phone: '',
+ username: '',
+ password: '',
+ confirm: '',
+ isValidPhone: false,
+ isValidUsername: false,
+ isValidPassword: false,
+ passwordsMatch: false,
+ tcAccepted: false,
+ attemptedSubmit: false,
+ });
+
+ /*
+ * 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.isValidUsername && form.isValidPassword && form.passwordsMatch) {
+ if (form.tcAccepted) {
+ let registerResponse = await fetch(REGISTER_ENDPOINT, {
+ method: 'POST',
+ body: JSON.stringify({
+ first_name: fname,
+ last_name: lname,
+ email: email,
+ phone_number: phone,
+ username: form.username,
+ password: form.password,
+ }),
+ });
+ let statusCode = registerResponse.status;
+ let data = await registerResponse.json();
+ const userId: string = data.UserID;
+ if (statusCode === 201) {
+ try {
+ await AsyncStorage.setItem('token', data.token);
+ /*
+ * Skipping navigation to Checkpoint for alpha
+ * navigation.navigate('Checkpoint', { userId: userId, username: form.username });
+ */
+ navigation.navigate('ProfileOnboarding', {
+ userId: userId,
+ username: form.username,
+ });
+ } catch (err) {
+ console.log(err);
+ }
+ } else if (statusCode === 409) {
+ Alert.alert(ERROR_REGISTRATION(data));
+ } 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 = () => (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="backward"
+ onPress={() => navigation.navigate('RegistrationTwo', {phone: phone})}
+ />
+ <TouchableOpacity onPress={handleRegister}>
+ <ArrowButton
+ direction="forward"
+ disabled={
+ !(
+ form.isValidUsername &&
+ form.isValidPassword &&
+ form.passwordsMatch &&
+ form.tcAccepted
+ )
+ }
+ onPress={handleRegister}
+ />
+ </TouchableOpacity>
+ </View>
+ );
+
+ return (
+ <Background
+ style={styles.container}
+ gradientType={BackgroundGradientType.Light}>
+ <StatusBar barStyle="light-content" />
+ <RegistrationWizard style={styles.wizard} step="five" />
+ <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 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: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ formHeader: {
+ color: '#fff',
+ fontSize: 30,
+ fontWeight: '600',
+ marginBottom: '16%',
+ },
+ tc: {
+ marginVertical: '5%',
+ top: '8%',
+ },
+ load: {
+ top: '5%',
+ },
+ footer: {
+ width: '100%',
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ ...Platform.select({
+ ios: {
+ bottom: '20%',
+ },
+ android: {
+ bottom: '10%',
+ },
+ }),
+ },
+});
+
+export default RegistrationThree;
diff --git a/src/screens/onboarding/legacy/RegistrationTwo.tsx b/src/screens/onboarding/legacy/RegistrationTwo.tsx
new file mode 100644
index 00000000..2dd86f90
--- /dev/null
+++ b/src/screens/onboarding/legacy/RegistrationTwo.tsx
@@ -0,0 +1,275 @@
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import React, {useRef, useState} from 'react';
+import {
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {
+ ArrowButton,
+ Background,
+ RegistrationWizard,
+ TaggInput,
+} from '../../components';
+import {emailRegex, nameRegex} from '../../constants';
+import {ERROR_NEXT_PAGE} from '../../constants/strings';
+import {OnboardingStackParams} from '../../routes';
+import {BackgroundGradientType} from '../../types';
+
+type RegistrationScreenTwoRouteProp = RouteProp<
+ OnboardingStackParams,
+ 'RegistrationTwo'
+>;
+type RegistrationScreenTwoNavigationProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'RegistrationTwo'
+>;
+interface RegistrationTwoProps {
+ route: RegistrationScreenTwoRouteProp;
+ navigation: RegistrationScreenTwoNavigationProp;
+}
+/**
+ * Registration screen 2 for First Name and Last Name
+ * @param navigation react-navigation navigation object
+ */
+const RegistrationTwo: React.FC<RegistrationTwoProps> = ({
+ route,
+ navigation,
+}) => {
+ // refs for changing focus
+ const lnameRef = useRef();
+ const emailRef = useRef();
+
+ const params = route.params;
+ const phone: string = params!.phone;
+ /**
+ * 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;
+ default:
+ return;
+ }
+ };
+
+ // registration form state
+ const [form, setForm] = useState({
+ fname: '',
+ lname: '',
+ email: '',
+ isValidFname: false,
+ isValidLname: false,
+ isValidEmail: false,
+ attemptedSubmit: false,
+ token: '',
+ });
+
+ /*
+ * Handles changes to the first name field value and verifies the input by updating state and running a validation function.
+ */
+ const handleFnameUpdate = (fname: string) => {
+ fname = fname.trim();
+ let isValidFname: boolean = nameRegex.test(fname);
+ 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) => {
+ lname = lname.trim();
+ let isValidLname: boolean = nameRegex.test(lname);
+ 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) => {
+ email = email.trim();
+ let isValidEmail: boolean = emailRegex.test(email);
+ setForm({
+ ...form,
+ email,
+ isValidEmail,
+ });
+ };
+
+ /**
+ * Handles a click on the "next" arrow button by navigating to RegistrationThree if First Name and Last Name are filled
+ */
+ const goToRegisterThree = async () => {
+ if (!form.attemptedSubmit) {
+ setForm({
+ ...form,
+ attemptedSubmit: true,
+ });
+ }
+ try {
+ if (form.isValidFname && form.isValidLname && form.isValidEmail) {
+ navigation.navigate('RegistrationThree', {
+ firstName: form.fname,
+ lastName: form.lname,
+ email: form.email,
+ phone: phone,
+ });
+ } 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 = () => (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="backward"
+ onPress={() => navigation.navigate('RegistrationOne')}
+ />
+ <TouchableOpacity onPress={goToRegisterThree}>
+ <ArrowButton
+ direction="forward"
+ disabled={!(form.isValidFname && form.isValidLname)}
+ onPress={goToRegisterThree}
+ />
+ </TouchableOpacity>
+ </View>
+ );
+
+ return (
+ <Background
+ style={styles.container}
+ gradientType={BackgroundGradientType.Light}>
+ <StatusBar barStyle="light-content" />
+ <RegistrationWizard style={styles.wizard} step="four" />
+ <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}
+ onSubmitEditing={goToRegisterThree}
+ />
+ <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}
+ onSubmitEditing={goToRegisterThree}
+ />
+ </KeyboardAvoidingView>
+ <Footer />
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ 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 RegistrationTwo;
diff --git a/src/screens/onboarding/legacy/SocialMedia.tsx b/src/screens/onboarding/legacy/SocialMedia.tsx
new file mode 100644
index 00000000..c1edef00
--- /dev/null
+++ b/src/screens/onboarding/legacy/SocialMedia.tsx
@@ -0,0 +1,161 @@
+import { RouteProp } from '@react-navigation/native';
+import { StackNavigationProp } from '@react-navigation/stack';
+import React from 'react';
+import {
+ KeyboardAvoidingView,
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import {
+ Background,
+ LinkSocialMedia,
+ RegistrationWizard
+} from '../../../components';
+import { SOCIAL_LIST } from '../../../constants';
+import { OnboardingStackParams } from '../../../routes';
+import {
+ BackgroundGradientType,
+ CategorySelectionScreenType,
+ LinkerType
+} from '../../../types';
+
+/**
+ * Social Media Screen for displaying social media linkers
+ */
+
+type SocialMediaRouteProps = RouteProp<OnboardingStackParams, 'SocialMedia'>;
+
+type SocialMediaNavigationProps = StackNavigationProp<
+ OnboardingStackParams,
+ 'SocialMedia'
+>;
+
+interface SocialMediaProps {
+ route: SocialMediaRouteProps;
+ navigation: SocialMediaNavigationProps;
+}
+
+const SocialMedia: React.FC<SocialMediaProps> = ({route, navigation}) => {
+ const {userId, username} = route.params;
+ const linkers: Array<LinkerType> = [];
+
+ // let numSocials: Number = state.showMore ? 9 : 3;
+
+ for (let i = 0; i < SOCIAL_LIST.length; i++) {
+ let linker: LinkerType = {
+ label: SOCIAL_LIST[i],
+ };
+ linkers.push(linker);
+ }
+
+ /**
+ * Just commenting this out, in case we need it in the future
+ */
+ // const handleShowPress = () => {
+ // setState({
+ // ...state,
+ // showMore: !state.showMore,
+ // });
+ // };
+
+ const handleNext = () => {
+ navigation.navigate('CategorySelection', {
+ screenType: CategorySelectionScreenType.Onboarding,
+ user: {userId: userId, username: username},
+ newCustomCategory: undefined,
+ });
+ };
+
+ return (
+ <Background
+ style={styles.container}
+ gradientType={BackgroundGradientType.Light}>
+ <StatusBar barStyle="light-content" />
+ <KeyboardAvoidingView
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
+ style={styles.container}>
+ <RegistrationWizard style={styles.wizard} step="seven" />
+ <View style={styles.headerContainer}>
+ <Text style={styles.header}>SOCIAL MEDIA</Text>
+ <Text style={styles.subtext}>
+ Select the social media you want to add
+ </Text>
+ </View>
+ <View style={styles.linkerContainer}>
+ {linkers.map((linker, index) => (
+ <LinkSocialMedia key={index} social={linker} />
+ ))}
+ </View>
+ </KeyboardAvoidingView>
+ <TouchableOpacity onPress={handleNext} style={styles.nextButton}>
+ <Text style={styles.nextButtonLabel}>Next</Text>
+ </TouchableOpacity>
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ },
+ headerContainer: {
+ marginTop: '28%',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ linkerContainer: {
+ bottom: '15%',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ alignContent: 'center',
+ marginBottom: '10%',
+ },
+ header: {
+ color: '#fff',
+ fontSize: 22,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '4%',
+ },
+ subtext: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '35%',
+ marginHorizontal: '10%',
+ },
+ nextButton: {
+ backgroundColor: '#8F01FF',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 150,
+ height: 40,
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#8F01FF',
+ marginBottom: '15%',
+ },
+ nextButtonLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#ddd',
+ },
+});
+
+export default SocialMedia;
diff --git a/src/screens/onboarding/legacy/TaggPopup.tsx b/src/screens/onboarding/legacy/TaggPopup.tsx
new file mode 100644
index 00000000..e71a4d2a
--- /dev/null
+++ b/src/screens/onboarding/legacy/TaggPopup.tsx
@@ -0,0 +1,143 @@
+import {BlurView} from '@react-native-community/blur';
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import * as React from 'react';
+import {Platform, StyleSheet, Text, TouchableOpacity} from 'react-native';
+import {Image, View} from 'react-native-animatable';
+import {ArrowButton} from '../../../components/onboarding';
+import {OnboardingStackParams} from '../../../routes';
+import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../../utils';
+import CloseIcon from '../../../assets/ionicons/close-outline.svg';
+
+type TaggPopupRouteProps = RouteProp<OnboardingStackParams, 'TaggPopup'>;
+type TaggPopupNavigationProps = StackNavigationProp<
+ OnboardingStackParams,
+ 'TaggPopup'
+>;
+
+interface TaggPopupProps {
+ route: TaggPopupRouteProps;
+ navigation: TaggPopupNavigationProps;
+}
+
+const TaggPopup: React.FC<TaggPopupProps> = ({route, navigation}) => {
+ /**
+ * Custom popup / Tutorial screen for Tagg
+ * Just like a Singly Linked List, we have a next node
+ * if (next !== undefined)
+ * Display the next button and navigate to next popup node on click
+ * else
+ * Display close button, navigate back on close
+ */
+ const {messageHeader, messageBody, next} = route.params.popupProps;
+
+ return (
+ <BlurView blurType="light" blurAmount={2} style={styles.container}>
+ <TouchableOpacity
+ style={styles.container}
+ onPressOut={() => {
+ navigation.goBack();
+ }}>
+ <View style={styles.popup}>
+ <Image
+ style={styles.icon}
+ source={require('../../../assets/icons/notificationPrompts/plus-logo.png')}
+ />
+ <View style={styles.textContainer}>
+ <Text style={styles.header}>{messageHeader}</Text>
+ <Text style={styles.subtext}>{messageBody}</Text>
+ </View>
+ {!next && (
+ <TouchableOpacity
+ style={styles.closeButton}
+ onPress={() => {
+ navigation.goBack();
+ }}>
+ <CloseIcon height={'50%'} width={'50%'} color={'white'} />
+ </TouchableOpacity>
+ )}
+ </View>
+ {next && (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="forward"
+ onPress={() => {
+ navigation.navigate('TaggPopup', {popupProps: next});
+ }}
+ />
+ </View>
+ )}
+ </TouchableOpacity>
+ </BlurView>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '100%',
+ height: '100%',
+ },
+ whiteColor: {
+ color: 'white',
+ },
+ closeButton: {
+ position: 'relative',
+ height: '50%',
+ aspectRatio: 1,
+ left: '20%',
+ },
+ textContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ },
+ icon: {
+ width: 40,
+ height: 40,
+ marginVertical: '1%',
+ },
+ header: {
+ color: '#fff',
+ fontSize: SCREEN_WIDTH / 25,
+ fontWeight: '600',
+ textAlign: 'justify',
+ marginBottom: '2%',
+ marginLeft: '4%',
+ },
+ subtext: {
+ color: '#fff',
+ fontSize: SCREEN_WIDTH / 30,
+ fontWeight: '600',
+ textAlign: 'justify',
+ marginBottom: '15%',
+ marginLeft: '3%',
+ },
+ popup: {
+ width: SCREEN_WIDTH * 0.8,
+ height: SCREEN_WIDTH * 0.24,
+ backgroundColor: 'black',
+ borderRadius: 8,
+ flexDirection: 'row',
+ alignSelf: 'auto',
+ flexWrap: 'wrap',
+ position: 'absolute',
+ bottom: SCREEN_HEIGHT * 0.7,
+ padding: SCREEN_WIDTH / 40,
+ },
+ footer: {
+ marginLeft: '50%',
+ flexDirection: 'column-reverse',
+ ...Platform.select({
+ ios: {
+ bottom: '20%',
+ },
+ android: {
+ bottom: '10%',
+ },
+ }),
+ },
+});
+export default TaggPopup;
diff --git a/src/screens/onboarding/legacy/WaitlistSuccessScreen.tsx b/src/screens/onboarding/legacy/WaitlistSuccessScreen.tsx
new file mode 100644
index 00000000..39e3b3b9
--- /dev/null
+++ b/src/screens/onboarding/legacy/WaitlistSuccessScreen.tsx
@@ -0,0 +1,156 @@
+import { StackNavigationProp } from '@react-navigation/stack';
+import React from 'react';
+import {
+ KeyboardAvoidingView,
+ Linking,
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import { ArrowButton, Background } from '../../../components';
+import { TAGG_WEBSITE } from '../../../constants';
+import { OnboardingStackParams } from '../../../routes';
+import { BackgroundGradientType } from '../../../types';
+import { SCREEN_HEIGHT } from '../../../utils';
+import CelebrationLogo from '../../assets/icons/celebration-logo.svg';
+
+type WaitlistSuccessScreenProp = StackNavigationProp<
+ OnboardingStackParams,
+ 'WaitlistSuccess'
+>;
+
+interface WaitlistSuccessScreenProps {
+ navigation: WaitlistSuccessScreenProp;
+}
+
+const WaitlistSuccessScreen: React.FC<WaitlistSuccessScreenProps> = ({
+ navigation,
+}) => {
+ const handleSignIn = () => {
+ navigation.navigate('InvitationCodeVerification');
+ };
+
+ const Footer = () => (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="backward"
+ onPress={() => navigation.navigate('AddWaitlistUser')}
+ />
+ </View>
+ );
+ return (
+ <Background
+ style={styles.container}
+ gradientType={BackgroundGradientType.Light}>
+ <KeyboardAvoidingView
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
+ style={styles.container}>
+ <StatusBar barStyle="light-content" />
+ <CelebrationLogo width={100} height={100} />
+ <Text style={styles.heading}>
+ You've successfully joined{'\n'}
+ the waitlist, we'll let you know{'\n'}
+ as soon as your invite is{'\n'}ready!
+ </Text>
+ <Text style={[styles.subHeading, styles.subHeadOneMargin]}>
+ To learn more about Tagg you can visit our{'\n'}{' '}
+ <Text
+ style={styles.link}
+ onPress={() => {
+ Linking.openURL(TAGG_WEBSITE);
+ }}>
+ website
+ </Text>
+ . Thank you!
+ </Text>
+ <Text style={[styles.subHeading, styles.subHeadTwoMargin]}>
+ Got your invite text?
+ </Text>
+ <TouchableOpacity onPress={handleSignIn} style={styles.finalAction}>
+ <Text style={styles.finalActionLabel}>Sign In</Text>
+ </TouchableOpacity>
+ </KeyboardAvoidingView>
+ <Footer />
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ link: {
+ textDecorationLine: 'underline',
+ },
+ finalAction: {
+ backgroundColor: 'white',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 150,
+ height: 40,
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#fff',
+ marginBottom: SCREEN_HEIGHT / 20,
+ marginTop: SCREEN_HEIGHT / 45,
+ },
+ finalActionLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: 'black',
+ },
+ formHeader: {
+ color: '#fff',
+ fontSize: 30,
+ fontWeight: '600',
+ marginBottom: '16%',
+ },
+ footer: {
+ width: '100%',
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ ...Platform.select({
+ ios: {
+ bottom: '20%',
+ },
+ android: {
+ bottom: '10%',
+ },
+ }),
+ },
+ heading: {
+ fontSize: 25,
+ fontWeight: 'bold',
+ color: 'white',
+ marginTop: SCREEN_HEIGHT / 25,
+ textAlign: 'center',
+ },
+ subHeading: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ subHeadOneMargin: {
+ marginTop: SCREEN_HEIGHT / 30,
+ },
+ subHeadTwoMargin: {
+ marginTop: SCREEN_HEIGHT / 10,
+ },
+});
+
+export default WaitlistSuccessScreen;