diff options
| author | Ivan Chen <ivan@tagg.id> | 2021-04-21 15:43:03 -0400 |
|---|---|---|
| committer | Ivan Chen <ivan@tagg.id> | 2021-04-21 15:43:03 -0400 |
| commit | 0dd0a4ac4343df036a1f16cbde070c524405bd21 (patch) | |
| tree | 292a30ce0228560720214645805deb4b89f2eeae /src/screens/onboarding/legacy | |
| parent | 5c438df5f6787cdc6c393873a98590cc827667b9 (diff) | |
| parent | 4e8e1c0d58424e6b63cfb8470fc0a73c0e6b102b (diff) | |
Merge branch 'master' into hotfix-linting-fixup
# Conflicts:
# .eslintrc.js
# src/components/notifications/Notification.tsx
# src/screens/onboarding/legacy/SocialMedia.tsx
# src/screens/onboarding/legacy/WaitlistSuccessScreen.tsx
# src/screens/profile/CategorySelection.tsx
Diffstat (limited to 'src/screens/onboarding/legacy')
| -rw-r--r-- | src/screens/onboarding/legacy/AddWaitlistUserScreen.tsx | 237 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/Checkpoint.tsx | 142 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/ProfileOnboarding.tsx | 575 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/RegistrationOne.tsx | 211 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/RegistrationThree.tsx | 363 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/RegistrationTwo.tsx | 275 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/SocialMedia.tsx | 161 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/TaggPopup.tsx | 143 | ||||
| -rw-r--r-- | src/screens/onboarding/legacy/WaitlistSuccessScreen.tsx | 156 |
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; |
