From a2ef180e37211193f41ff5f02e18279e5242c219 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Fri, 7 May 2021 14:21:22 -0400 Subject: renamed onboarding screens, fixed various minor issue, basic info onboarding working --- src/constants/strings.ts | 5 +- src/routes/onboarding/OnboardingStackNavigator.tsx | 6 +- src/routes/onboarding/OnboardingStackScreen.tsx | 24 +- src/screens/onboarding/BasicInfoOnboarding.tsx | 460 +++++++++++++++++++++ src/screens/onboarding/Login.tsx | 2 +- src/screens/onboarding/OnboardingStepOne.tsx | 286 ------------- src/screens/onboarding/OnboardingStepThree.tsx | 380 ----------------- src/screens/onboarding/OnboardingStepTwo.tsx | 371 ----------------- src/screens/onboarding/PhoneVerification.tsx | 7 +- src/screens/onboarding/ProfileInfoOnboarding.tsx | 380 +++++++++++++++++ src/screens/onboarding/RevampedOnboarding.tsx | 458 -------------------- src/screens/onboarding/WelcomeScreen.tsx | 2 +- src/screens/onboarding/index.ts | 7 +- .../onboarding/legacy/OnboardingStepOne.tsx | 286 +++++++++++++ .../onboarding/legacy/OnboardingStepTwo.tsx | 368 +++++++++++++++++ 15 files changed, 1515 insertions(+), 1527 deletions(-) create mode 100644 src/screens/onboarding/BasicInfoOnboarding.tsx delete mode 100644 src/screens/onboarding/OnboardingStepOne.tsx delete mode 100644 src/screens/onboarding/OnboardingStepThree.tsx delete mode 100644 src/screens/onboarding/OnboardingStepTwo.tsx create mode 100644 src/screens/onboarding/ProfileInfoOnboarding.tsx delete mode 100644 src/screens/onboarding/RevampedOnboarding.tsx create mode 100644 src/screens/onboarding/legacy/OnboardingStepOne.tsx create mode 100644 src/screens/onboarding/legacy/OnboardingStepTwo.tsx (limited to 'src') diff --git a/src/constants/strings.ts b/src/constants/strings.ts index e8f2725d..56d54d39 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -45,6 +45,7 @@ export const ERROR_SERVER_DOWN = 'mhm, looks like our servers are down, please r export const ERROR_SOMETHING_WENT_WRONG = 'Oh dear, don’t worry someone will be held responsible for this error, In the meantime refresh the app'; export const ERROR_SOMETHING_WENT_WRONG_REFRESH = "Ha, looks like this one's on us, please refresh and try again"; export const ERROR_SOMETHING_WENT_WRONG_RELOAD = "You broke it, Just kidding! we don't know what happened... Please reload the app and try again"; +export const ERROR_T_AND_C_NOT_ACCEPTED = 'You must first agree to the terms and conditions.'; export const ERROR_TWILIO_SERVER_ERROR = 'mhm, looks like that is an invalid phone number or our servers are down, please try again in a few mins'; export const ERROR_UNABLE_CONNECT_CHAT = 'Unable to connect chat'; export const ERROR_UNABLE_TO_FIND_PROFILE = 'We were unable to find this profile. Please check username and try again'; @@ -57,17 +58,17 @@ export const ERROR_UPLOAD_SMALL_PROFILE_PIC = "Can't have a profile without a pi export const ERROR_UPLOAD_SP_PHOTO = 'Unable to update suggested people photo. Please retry!'; export const ERROR_VERIFICATION_FAILED_SHORT = 'Verification failed 😓'; export const FIRST_MESSAGE = 'How about sending your first message to your friend'; -export const START_CHATTING = 'Let’s Start Chatting!'; export const MARKED_AS_MSG = (str: string) => `Marked as ${str}`; export const MOMENT_DELETED_MSG = 'Moment deleted....Some moments have to go, to create space for greater ones'; export const NO_NEW_NOTIFICATIONS = 'You have no new notifications'; export const NO_RESULTS_FOUND = 'No Results Found!'; export const PRIVATE_ACCOUNT = 'This account is private'; +export const START_CHATTING = 'Let’s Start Chatting!'; export const SUCCESS_BADGES_UPDATE = 'Badges updated successfully!' export const SUCCESS_CATEGORY_DELETE = 'Category successfully deleted, but its memory will live on'; -export const SUCCESS_LAST_CONTACT_INVITE = 'Done! That was your last invite, hope you used it wisely!'; export const SUCCESS_INVITATION_CODE = 'Welcome to Tagg!'; export const SUCCESS_INVITE_CONTACT = (str: string) => `Success! You now have ${str} invites left!`; +export const SUCCESS_LAST_CONTACT_INVITE = 'Done! That was your last invite, hope you used it wisely!'; export const SUCCESS_LINK = (str: string) => `Successfully linked ${str} 🎉`; export const SUCCESS_PIC_UPLOAD = 'Beautiful, the picture was uploaded successfully!'; export const SUCCESS_PWD_RESET = 'Your password was reset successfully!'; diff --git a/src/routes/onboarding/OnboardingStackNavigator.tsx b/src/routes/onboarding/OnboardingStackNavigator.tsx index 761efc91..7a74988b 100644 --- a/src/routes/onboarding/OnboardingStackNavigator.tsx +++ b/src/routes/onboarding/OnboardingStackNavigator.tsx @@ -1,12 +1,10 @@ import {createStackNavigator} from '@react-navigation/stack'; export type OnboardingStackParams = { - RevampedOnboarding: {isPhoneVerified: boolean}; InvitationCodeVerification: {userId: string; username: string}; Login: undefined; - OnboardingStepOne: undefined; - OnboardingStepThree: {userId: string; username: string}; - OnboardingStepTwo: {firstName: string}; + BasicInfoOnboarding: {isPhoneVerified: boolean}; + ProfileInfoOnboarding: {userId: string; username: string}; PasswordReset: {value: string}; PasswordResetRequest: undefined; PasswordVerification: {id: string}; diff --git a/src/routes/onboarding/OnboardingStackScreen.tsx b/src/routes/onboarding/OnboardingStackScreen.tsx index 458697ad..50fd52d6 100644 --- a/src/routes/onboarding/OnboardingStackScreen.tsx +++ b/src/routes/onboarding/OnboardingStackScreen.tsx @@ -1,18 +1,16 @@ import {StackCardInterpolationProps} from '@react-navigation/stack'; import React from 'react'; import { + BasicInfoOnboarding, InvitationCodeVerification, Login, - OnboardingStepThree, - OnboardingStepTwo, PasswordReset, PasswordResetRequest, PasswordVerification, PhoneVerification, + ProfileInfoOnboarding, WelcomeScreen, - RevampedOnboarding } from '../../screens'; -import OnboardingStepOne from '../../screens/onboarding/OnboardingStepOne'; import {modalStyle} from '../main'; import {OnboardingStack} from './OnboardingStackNavigator'; @@ -44,7 +42,10 @@ const Onboarding: React.FC = () => { }} /> - + { ...modalStyle, }} /> - - ; +type BasicInfoOnboardingNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'BasicInfoOnboarding' +>; +interface BasicInfoOnboardingProps { + route: BasicInfoOnboardingRouteProp; + navigation: BasicInfoOnboardingNavigationProp; +} + +const BasicInfoOnboarding: React.FC = ({route}) => { + const {isPhoneVerified} = route.params; + const navigation = useNavigation(); + const [attemptedSubmit, setAttemptedSubmit] = useState(false); + const [valid, setValid] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [tcAccepted, setTCAccepted] = useState(false); + const [passVisibility, setPassVisibility] = useState(false); + const [form, setForm] = useState({ + fname: '', + lname: '', + username: '', + phone: '', + email: '', + password: '', + }); + + const goNext = async () => { + if (!attemptedSubmit) { + setAttemptedSubmit(true); + } + try { + if (valid) { + const {phone} = form; + const code = await sendOtpStatusCode(phone); + if (code) { + switch (code) { + case 200: + const {fname, lname} = form; + navigation.navigate('PhoneVerification', { + firstName: fname, + lastName: lname, + phone, + }); + break; + case 409: + Alert.alert(ERROR_PHONE_IN_USE); + break; + default: + Alert.alert(ERROR_TWILIO_SERVER_ERROR); + } + } else { + setAttemptedSubmit(false); + setTimeout(() => { + setAttemptedSubmit(true); + }); + } + } + } catch (error) { + Alert.alert(ERROR_NEXT_PAGE); + return { + name: 'Navigation error', + description: error, + }; + } + }; + // 0 = first name, 1 = last name, 2 = username, 3 = phone # + const handleNameUpdate = (name: string, nameType: number) => { + name = name.trim(); + let isValidName: boolean = nameRegex.test(name); + switch (nameType) { + case 0: + setForm({ + ...form, + fname: name, + }); + setValid(isValidName); + break; + case 1: + setForm({ + ...form, + lname: name, + }); + setValid(isValidName); + break; + case 2: + setForm({ + ...form, + username: name, + }); + setValid(usernameRegex.test(name)); + break; + } + }; + const handlePhoneUpdate = (phone: string) => { + phone = phone.trim(); + setForm({ + ...form, + phone, + }); + setValid(phoneRegex.test(phone)); + }; + const handleEmailUpdate = (email: string) => { + email = email.trim(); + setForm({ + ...form, + email, + }); + setValid(emailRegex.test(email)); + }; + const handlePasswordUpdate = (password: string) => { + setForm({ + ...form, + password, + }); + setValid(passwordRegex.test(password)); + }; + const formSteps: { + placeholder: string; + onChangeText: (text: string) => void; + }[] = [ + { + placeholder: 'First Name', + onChangeText: (text) => handleNameUpdate(text, 0), + }, + { + placeholder: 'Last Name', + onChangeText: (text) => handleNameUpdate(text, 1), + }, + { + placeholder: 'Phone', + onChangeText: handlePhoneUpdate, + }, + { + placeholder: 'School Email', + onChangeText: handleEmailUpdate, + }, + { + placeholder: 'Username', + onChangeText: (text) => handleNameUpdate(text, 2), + }, + { + placeholder: 'Password', + onChangeText: handlePasswordUpdate, + }, + ]; + const resetForm = (formStep: String) => { + setValid(false); + switch (formStep) { + case 'First Name': + setForm({ + ...form, + fname: '', + }); + break; + case 'Last Name': + setForm({ + ...form, + lname: '', + }); + break; + case 'Email': + setForm({ + ...form, + email: '', + }); + break; + case 'Password': + setForm({ + ...form, + password: '', + }); + break; + case 'School Email': + setForm({ + ...form, + email: '', + }); + break; + case 'Username': + setForm({ + ...form, + username: '', + }); + break; + } + }; + const step = formSteps[currentStep]; + const advance = () => { + setAttemptedSubmit(true); + if (valid) { + setCurrentStep(currentStep + 1); + setAttemptedSubmit(false); + setValid(false); + } + }; + const advanceRegistration = async () => { + setAttemptedSubmit(true); + if (!valid) { + return; + } + if (!tcAccepted) { + Alert.alert('Terms and conditions', ERROR_T_AND_C_NOT_ACCEPTED); + return; + } + const {fname, lname, phone, email, username, password} = form; + const response = await sendRegister( + fname, + lname, + phone, + email, + username, + password, + ); + if (response) { + const data = await response.json(); + const {token, UserID} = data; + switch (response.status) { + case 201: + await AsyncStorage.setItem('token', token); + navigation.navigate('ProfileInfoOnboarding', { + userId: UserID, + username: username, + }); + break; + case 400: + Alert.alert(ERROR_REGISTRATION(data.toLowerCase())); + break; + default: + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + break; + } + } else { + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + } + }; + + useEffect(() => { + if (isPhoneVerified) { + advance(); + } + }, [isPhoneVerified]); + + return ( + + + {/* getting rid of registration progress in onboarding*/} + {/* */} + + + {currentStep !== 0 && currentStep !== 3 && ( + { + // if I go back do I want to reset the previous form? + setCurrentStep(currentStep - 1); + resetForm(step.placeholder); + setAttemptedSubmit(false); + }} + /> + )} + + {step.placeholder === 'Phone' && !isPhoneVerified ? ( + <> + + + + ) : ( + <> + {step.placeholder !== 'Password' ? ( + <> + SIGN UP + + + + + + ) : ( + <> + + setPassVisibility(!passVisibility)}> + Show Password + + + + + )} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + alignItems: 'center', + justifyContent: 'center', + }, + input: { + minWidth: '60%', + height: 40, + fontSize: 16, + fontWeight: '600', + color: '#fff', + paddingLeft: 13, + borderBottomWidth: 1, + borderBottomColor: '#fff', + }, + button: { + width: 40, + aspectRatio: 10, + }, + formHeader: { + color: '#fff', + fontSize: 30, + fontWeight: '600', + position: 'absolute', + top: '20%', + }, + load: { + top: '5%', + }, + tc: { + marginVertical: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); +export default BasicInfoOnboarding; diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 3b970864..6922bbc7 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -182,7 +182,7 @@ const Login: React.FC = ({navigation}: LoginProps) => { * finish step 3, thus does not have a universtiy. * Redirecting user back to onboarding to finish the process */ - navigation.navigate('OnboardingStepThree', { + navigation.navigate('ProfileInfoOnboarding', { userId: data.UserID, username: username, }); diff --git a/src/screens/onboarding/OnboardingStepOne.tsx b/src/screens/onboarding/OnboardingStepOne.tsx deleted file mode 100644 index abec050e..00000000 --- a/src/screens/onboarding/OnboardingStepOne.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useMemo, useRef, useState} from 'react'; -import { - Alert, - KeyboardAvoidingView, - Platform, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { - ArrowButton, - Background, - BasicButton, - // RegistrationWizard, - TaggInput, - TaggSquareButton, -} from '../../components'; -import {nameRegex, phoneRegex} from '../../constants'; -import { - ERROR_NEXT_PAGE, - ERROR_PHONE_IN_USE, - ERROR_TWILIO_SERVER_ERROR, -} from '../../constants/strings'; -import {OnboardingStackParams} from '../../routes'; -import {sendOtpStatusCode} from '../../services'; -import {BackgroundGradientType} from '../../types'; -import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; - -type OnboardingStepOneNavigationProp = StackNavigationProp< - OnboardingStackParams, - 'OnboardingStepOne' ->; -interface OnboardingStepOneProps { - navigation: OnboardingStepOneNavigationProp; -} - -const OnboardingStepOne: React.FC = ({navigation}) => { - const lnameRef = useRef(); - const emailRef = useRef(); - const phoneRef = useRef(); - - const handleFocusChange = (field: string): void => { - switch (field) { - case 'lname': - const lnameField: any = lnameRef.current; - lnameField.focus(); - break; - case 'email': - const emailField: any = emailRef.current; - emailField.focus(); - break; - case 'phone': - const phoneField: any = phoneRef.current; - phoneField.focus(); - break; - default: - return; - } - }; - - const [form, setForm] = useState({ - fname: '', - lname: '', - phone: '', - isValidFname: false, - isValidLname: false, - isValidPhone: false, - attemptedSubmit: false, - token: '', - }); - - const handleFnameUpdate = (fname: string) => { - fname = fname.trim(); - let isValidFname: boolean = nameRegex.test(fname); - setForm({ - ...form, - fname, - isValidFname, - }); - }; - - const handleLnameUpdate = (lname: string) => { - lname = lname.trim(); - let isValidLname: boolean = nameRegex.test(lname); - setForm({ - ...form, - lname, - isValidLname, - }); - }; - - const handlePhoneUpdate = (phone: string) => { - phone = phone.trim(); - let isValidPhone: boolean = phoneRegex.test(phone); - setForm({ - ...form, - phone, - isValidPhone, - }); - }; - - const goNext = async () => { - if (!form.attemptedSubmit) { - setForm({ - ...form, - attemptedSubmit: true, - }); - } - try { - const {isValidFname} = form; - // const {isValidFname, isValidLname, isValidPhone} = form; - if (isValidFname) { - const {fname} = form; - navigation.navigate('OnboardingStepTwo', { - firstName: fname - }); - // const {phone} = form; - // const code = await sendOtpStatusCode(phone); - // if (code) { - // switch (code) { - // case 200: - // const {fname, lname} = form; - - // break; - // case 409: - // Alert.alert(ERROR_PHONE_IN_USE); - // break; - // default: - // Alert.alert(ERROR_TWILIO_SERVER_ERROR); - } else { - setForm({...form, attemptedSubmit: false}); - setTimeout(() => setForm({...form, attemptedSubmit: true})); - } - } catch (error) { - Alert.alert(ERROR_NEXT_PAGE); - return { - name: 'Navigation error', - description: error, - }; - } - }; - - const footer = useMemo( - () => ( - - // // - // {/* navigation.navigate('Login')} - // /> */} - // {/* */} - // {/* */} - // {/* */} - // // - ), - [ - form.fname, - // form.lname, - // form.phone, - form.isValidFname, - // form.isValidLname, - // form.isValidPhone, - ], - ); - - return ( - - - {/* getting rid of registration progress in onboarding*/} - {/* */} - - {/* */} - SIGN UP - {/* */} - handleFocusChange('lname')} - blurOnSubmit={false} - valid={form.isValidFname} - invalidWarning="Please enter a valid first name." - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - {/* */} - {/* */} - - {footer} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - height: SCREEN_HEIGHT, - width: SCREEN_WIDTH, - alignItems: 'center', - justifyContent: 'center', - }, - button: { - width: 40, - aspectRatio: 10, - }, - formHeader: { - color: '#fff', - fontSize: 30, - fontWeight: '600', - marginBottom: '16%', - position: 'absolute', top: 0, marginTop: '45%', - }, - load: { - top: '5%', - }, - footer: { - width: '100%', - flexDirection: 'row', - justifyContent: 'space-around', - ...Platform.select({ - ios: { - bottom: '20%', - }, - android: { - bottom: '10%', - }, - }), - }, -}); - -export default OnboardingStepOne; diff --git a/src/screens/onboarding/OnboardingStepThree.tsx b/src/screens/onboarding/OnboardingStepThree.tsx deleted file mode 100644 index 6421c396..00000000 --- a/src/screens/onboarding/OnboardingStepThree.tsx +++ /dev/null @@ -1,380 +0,0 @@ -import {RouteProp} from '@react-navigation/native'; -import {StackNavigationProp} from '@react-navigation/stack'; -import moment from 'moment'; -import React 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, - RegistrationWizard, - TaggDropDown, - TaggInput, - UniversitySelection, -} from '../../components'; -import {CLASS_YEAR_LIST, genderRegex, TAGG_PURPLE} from '../../constants'; -import { - ERROR_SELECT_BIRTHDAY, - ERROR_SELECT_CLASS_YEAR, - ERROR_SELECT_GENDER, - ERROR_SELECT_UNIVERSITY, -} from '../../constants/strings'; -import {OnboardingStackParams} from '../../routes/onboarding'; -import {patchEditProfile} from '../../services'; -import {BackgroundGradientType, UniversityType} from '../../types'; -import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; - -type OnboardingStepThreeRouteProp = RouteProp< - OnboardingStackParams, - 'OnboardingStepThree' ->; -type OnboardingStepThreeNavigationProp = StackNavigationProp< - OnboardingStackParams, - 'OnboardingStepThree' ->; -interface OnboardingStepThreeProps { - route: OnboardingStepThreeRouteProp; - navigation: OnboardingStepThreeNavigationProp; -} - -type FormType = { - smallPic: string; - university: UniversityType; - birthdate: string | undefined; - gender: string; - isValidGender: boolean; - classYear: number; - attemptedSubmit: boolean; -}; - -const OnboardingStepThree: React.FC = ({ - route, - navigation, -}) => { - const {userId} = route.params; - const [form, setForm] = React.useState({ - smallPic: '', - university: UniversityType.Empty, - birthdate: undefined, - gender: '', - isValidGender: true, - classYear: -1, - attemptedSubmit: false, - }); - const [customGender, setCustomGender] = React.useState(false); - - const classYearList = CLASS_YEAR_LIST.map((value) => ({ - label: value, - value, - })); - - /** - * Profile screen "Add profile picture" button - */ - const SmallProfilePic = () => ( - - {form.smallPic ? ( - - ) : ( - ADD PROFILE PICTURE - )} - - ); - - 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, - }); - } - }); - }; - - 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.classYear === -1) { - Alert.alert(ERROR_SELECT_CLASS_YEAR); - return; - } - if (form.university === UniversityType.Empty) { - Alert.alert(ERROR_SELECT_UNIVERSITY); - return; - } - if (!form.birthdate) { - Alert.alert(ERROR_SELECT_BIRTHDAY); - return; - } - if (form.gender === '') { - Alert.alert(ERROR_SELECT_GENDER); - return; - } - if (!form.attemptedSubmit) { - setForm({ - ...form, - attemptedSubmit: true, - }); - } - const request = new FormData(); - if (form.smallPic) { - request.append('smallProfilePicture', { - uri: form.smallPic, - name: 'small_profile_pic.jpg', - type: 'image/jpg', - }); - } - - if (customGender) { - if (form.isValidGender) { - request.append('gender', form.gender); - } else { - setForm({...form, attemptedSubmit: false}); - setTimeout(() => setForm({...form, attemptedSubmit: true})); - return; - } - } else { - if (form.isValidGender) { - request.append('gender', form.gender); - } - } - - request.append('birthday', form.birthdate); - request.append('university_class', form.classYear); - request.append('university', form.university); - - patchEditProfile(request, userId) - .then((_) => - navigation.navigate('InvitationCodeVerification', route.params), - ) - .catch((error) => { - Alert.alert(error); - }); - }; - - return ( - - - - - - - - - { - setForm({ - ...form, - university: selected, - }); - }} - /> - handleClassYearUpdate(value)} - items={classYearList} - placeholder={{ - label: 'Class Year', - value: null, - color: '#ddd', - }} - /> - - {customGender && ( - handleSubmit()} - valid={form.isValidGender} - attemptedSubmit={form.attemptedSubmit} - invalidWarning={ - 'Custom field can only contain letters and hyphens' - } - width={280} - /> - )} - handleGenderUpdate(value)} - items={[ - {label: 'Male', value: 'male'}, - {label: 'Female', value: 'female'}, - {label: 'Custom', value: 'custom'}, - ]} - placeholder={{ - label: 'Gender', - value: null, - color: '#ddd', - }} - /> - - - - Let's start! - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - height: SCREEN_HEIGHT, - }, - profile: { - marginTop: '10%', - marginBottom: '5%', - }, - contentContainer: { - position: 'relative', - width: 280, - }, - smallProfileUploader: { - justifyContent: 'center', - alignItems: 'center', - padding: 20, - backgroundColor: '#E1F0FF', - height: normalize(150), - width: normalize(150), - borderRadius: normalize(150), - }, - smallProfileText: { - textAlign: 'center', - fontSize: 14, - fontWeight: 'bold', - color: '#806DF4', - }, - smallProfilePic: { - height: normalize(150), - width: normalize(150), - borderRadius: normalize(150), - borderWidth: 2, - borderColor: 'white', - }, - submitBtn: { - backgroundColor: TAGG_PURPLE, - justifyContent: 'center', - alignItems: 'center', - width: SCREEN_WIDTH / 2.5, - height: SCREEN_WIDTH / 10, - borderRadius: 5, - marginTop: '5%', - alignSelf: 'center', - }, - submitBtnLabel: { - fontSize: 16, - fontWeight: '500', - color: '#fff', - }, - goBack: { - textDecorationLine: 'underline', - color: '#fff', - fontSize: 15, - fontWeight: '600', - }, - footer: { - marginTop: '3%', - alignItems: 'center', - justifyContent: 'space-around', - height: SCREEN_HEIGHT * 0.15, - }, - wizard: { - position: 'absolute', - top: SCREEN_HEIGHT * 0.1, - }, - purplePlus: { - position: 'absolute', - height: normalize(40), - width: normalize(40), - bottom: 0, - right: 0, - }, -}); - -export default OnboardingStepThree; diff --git a/src/screens/onboarding/OnboardingStepTwo.tsx b/src/screens/onboarding/OnboardingStepTwo.tsx deleted file mode 100644 index 5c474c14..00000000 --- a/src/screens/onboarding/OnboardingStepTwo.tsx +++ /dev/null @@ -1,371 +0,0 @@ -import AsyncStorage from '@react-native-community/async-storage'; -import {RouteProp} from '@react-navigation/native'; -import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useMemo, useRef, useState} from 'react'; -import { - Alert, - KeyboardAvoidingView, - Platform, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { - ArrowButton, - Background, - LoadingIndicator, - // RegistrationWizard, - TaggInput, - TaggSquareButton, - TermsConditions, -} from '../../components'; -import {emailRegex, passwordRegex, usernameRegex} from '../../constants'; -import { - ERROR_DOUBLE_CHECK_CONNECTION, - ERROR_REGISTRATION, - ERROR_SOMETHING_WENT_WRONG_REFRESH, -} from '../../constants/strings'; -import {OnboardingStackParams} from '../../routes'; -import {sendRegister} from '../../services'; -import {BackgroundGradientType} from '../../types'; -import {SCREEN_HEIGHT} from '../../utils'; - -type OnboardingStepTwoRouteProp = RouteProp< - OnboardingStackParams, - 'OnboardingStepTwo' ->; -type OnboardingStepTwoNavigationProp = StackNavigationProp< - OnboardingStackParams, - 'OnboardingStepTwo' ->; -interface OnboardingStepTwoProps { - route: OnboardingStepTwoRouteProp; - navigation: OnboardingStepTwoNavigationProp; -} - -const OnboardingStepTwo: React.FC = ({ - route, - navigation, -}) => { - const emailRef = useRef(); - const usernameRef = useRef(); - const passwordRef = useRef(); - const confirmRef = useRef(); - - const handleFocusChange = (field: string): void => { - switch (field) { - case 'email': - const emailField: any = emailRef.current; - emailField.focus(); - break; - case 'username': - const usernameField: any = usernameRef.current; - usernameField.focus(); - break; - case 'password': - const passwordField: any = passwordRef.current; - passwordField.focus(); - break; - case 'confirm': - const confirmField: any = confirmRef.current; - confirmField.focus(); - break; - default: - return; - } - }; - - // registration form state - const [form, setForm] = useState({ - email: '', - username: '', - password: '', - confirm: '', - isValidEmail: false, - isValidUsername: false, - isValidPassword: false, - passwordsMatch: false, - tcAccepted: false, - attemptedSubmit: false, - }); - - const handleEmailUpdate = (email: string) => { - email = email.trim(); - let isValidEmail: boolean = emailRegex.test(email); - setForm({ - ...form, - email, - isValidEmail, - }); - }; - - const handleUsernameUpdate = (username: string) => { - let isValidUsername: boolean = usernameRegex.test(username); - setForm({ - ...form, - username, - isValidUsername, - }); - }; - - const handlePasswordUpdate = (password: string) => { - let isValidPassword: boolean = passwordRegex.test(password); - let passwordsMatch: boolean = form.password === form.confirm; - setForm({ - ...form, - password, - isValidPassword, - passwordsMatch, - }); - }; - - const handleConfirmUpdate = (confirm: string) => { - let passwordsMatch: boolean = form.password === confirm; - setForm({ - ...form, - confirm, - passwordsMatch, - }); - }; - - const handleTcUpdate = (tcAccepted: boolean) => { - setForm({ - ...form, - tcAccepted, - }); - }; - - const handleRegister = async () => { - if (!form.attemptedSubmit) { - setForm({ - ...form, - attemptedSubmit: true, - }); - } - const { - isValidEmail, - isValidUsername, - isValidPassword, - passwordsMatch, - tcAccepted, - } = form; - try { - if ( - isValidEmail && - isValidUsername && - isValidPassword && - passwordsMatch - ) { - if (tcAccepted) { - const {email, username, password} = form; - const {firstName, lastName, phone} = route.params; - const response = await sendRegister( - firstName, - lastName, - phone, - email, - username, - password, - ); - if (response) { - const data = await response.json(); - const {token, UserID} = data; - switch (response.status) { - case 201: - await AsyncStorage.setItem('token', token); - navigation.navigate('OnboardingStepThree', { - userId: UserID, - username: username, - }); - break; - case 400: - Alert.alert(ERROR_REGISTRATION(data.toLowerCase())); - break; - default: - Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); - break; - } - } else { - Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); - } - } else { - Alert.alert( - 'Terms and conditions', - 'You must first agree to the terms and conditions.', - ); - } - } else { - setForm({...form, attemptedSubmit: false}); - setTimeout(() => setForm({...form, attemptedSubmit: true})); - } - } catch (error) { - Alert.alert(ERROR_REGISTRATION(ERROR_DOUBLE_CHECK_CONNECTION)); - return { - name: 'Registration error', - description: error, - }; - } - }; - - const footer = useMemo( - () => ( - - - - - - ), - [ - form.email, - form.username, - form.password, - form.confirm, - form.isValidEmail, - form.isValidUsername, - form.isValidPassword, - form.passwordsMatch, - form.tcAccepted, - ], - ); - - return ( - - - {/* */} - - - SIGN UP - - handleFocusChange('password')} - blurOnSubmit={false} - ref={usernameRef} - valid={form.isValidUsername} - invalidWarning={ - 'Username must be at least 6 characters and contain only alphanumerics.' - } - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - - handleFocusChange('confirm')} - blurOnSubmit={false} - secureTextEntry - ref={passwordRef} - valid={form.isValidPassword} - invalidWarning={ - 'Password must be at least 8 characters & contain at least one of a-z, A-Z, 0-9, and a special character.' - } - attemptedSubmit={form.attemptedSubmit} - width={280} - /> - - - - - {footer} - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - // wizard: { - // position: 'absolute', - // top: SCREEN_HEIGHT * 0.1, - // }, - formHeader: { - color: '#fff', - fontSize: 30, - fontWeight: '600', - marginBottom: '16%', - }, - tc: { - marginVertical: '5%', - }, - load: { - top: '5%', - }, - footer: { - width: '100%', - flexDirection: 'row', - justifyContent: 'space-around', - ...Platform.select({ - ios: { - bottom: '20%', - }, - android: { - bottom: '10%', - }, - }), - }, -}); - -export default OnboardingStepTwo; diff --git a/src/screens/onboarding/PhoneVerification.tsx b/src/screens/onboarding/PhoneVerification.tsx index 9b517d9d..93ae8fdc 100644 --- a/src/screens/onboarding/PhoneVerification.tsx +++ b/src/screens/onboarding/PhoneVerification.tsx @@ -31,7 +31,6 @@ import { import {OnboardingStackParams} from '../../routes'; import {sendOtp, verifyOtp} from '../../services'; import {BackgroundGradientType} from '../../types'; -import {SCREEN_HEIGHT} from '../../utils'; type PhoneVerificationRouteProp = RouteProp< OnboardingStackParams, @@ -66,7 +65,7 @@ const PhoneVerification: React.FC = ({ try { const success = await trackPromise(verifyOtp(phone, value)); if (success) { - navigation.navigate('RevampedOnboarding', {isPhoneVerified: true}); + navigation.navigate('BasicInfoOnboarding', {isPhoneVerified: true}); } } catch (error) { console.log(error); @@ -79,7 +78,9 @@ const PhoneVerification: React.FC = ({ navigation.navigate('RevampedOnboarding', {isPhoneVerified: false})} + onPress={() => + navigation.navigate('BasicInfoOnboarding', {isPhoneVerified: false}) + } /> ), diff --git a/src/screens/onboarding/ProfileInfoOnboarding.tsx b/src/screens/onboarding/ProfileInfoOnboarding.tsx new file mode 100644 index 00000000..a481b0c0 --- /dev/null +++ b/src/screens/onboarding/ProfileInfoOnboarding.tsx @@ -0,0 +1,380 @@ +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import moment from 'moment'; +import React 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, + RegistrationWizard, + TaggDropDown, + TaggInput, + UniversitySelection, +} from '../../components'; +import {CLASS_YEAR_LIST, genderRegex, TAGG_PURPLE} from '../../constants'; +import { + ERROR_SELECT_BIRTHDAY, + ERROR_SELECT_CLASS_YEAR, + ERROR_SELECT_GENDER, + ERROR_SELECT_UNIVERSITY, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes/onboarding'; +import {patchEditProfile} from '../../services'; +import {BackgroundGradientType, UniversityType} from '../../types'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +type ProfileInfoOnboardingRouteProp = RouteProp< + OnboardingStackParams, + 'ProfileInfoOnboarding' +>; +type ProfileInfoOnboardingNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'ProfileInfoOnboarding' +>; +interface ProfileInfoOnboardingProps { + route: ProfileInfoOnboardingRouteProp; + navigation: ProfileInfoOnboardingNavigationProp; +} + +type FormType = { + smallPic: string; + university: UniversityType; + birthdate: string | undefined; + gender: string; + isValidGender: boolean; + classYear: number; + attemptedSubmit: boolean; +}; + +const ProfileInfoOnboarding: React.FC = ({ + route, + navigation, +}) => { + const {userId} = route.params; + const [form, setForm] = React.useState({ + smallPic: '', + university: UniversityType.Empty, + birthdate: undefined, + gender: '', + isValidGender: true, + classYear: -1, + attemptedSubmit: false, + }); + const [customGender, setCustomGender] = React.useState(false); + + const classYearList = CLASS_YEAR_LIST.map((value) => ({ + label: value, + value, + })); + + /** + * Profile screen "Add profile picture" button + */ + const SmallProfilePic = () => ( + + {form.smallPic ? ( + + ) : ( + ADD PROFILE PICTURE + )} + + ); + + 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, + }); + } + }); + }; + + 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.classYear === -1) { + Alert.alert(ERROR_SELECT_CLASS_YEAR); + return; + } + if (form.university === UniversityType.Empty) { + Alert.alert(ERROR_SELECT_UNIVERSITY); + return; + } + if (!form.birthdate) { + Alert.alert(ERROR_SELECT_BIRTHDAY); + return; + } + if (form.gender === '') { + Alert.alert(ERROR_SELECT_GENDER); + return; + } + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + const request = new FormData(); + if (form.smallPic) { + request.append('smallProfilePicture', { + uri: form.smallPic, + name: 'small_profile_pic.jpg', + type: 'image/jpg', + }); + } + + if (customGender) { + if (form.isValidGender) { + request.append('gender', form.gender); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + return; + } + } else { + if (form.isValidGender) { + request.append('gender', form.gender); + } + } + + request.append('birthday', form.birthdate); + request.append('university_class', form.classYear); + request.append('university', form.university); + + patchEditProfile(request, userId) + .then((_) => + navigation.navigate('InvitationCodeVerification', route.params), + ) + .catch((error) => { + Alert.alert(error); + }); + }; + + return ( + + + + + + + + + { + setForm({ + ...form, + university: selected, + }); + }} + /> + handleClassYearUpdate(value)} + items={classYearList} + placeholder={{ + label: 'Class Year', + value: null, + color: '#ddd', + }} + /> + + {customGender && ( + handleSubmit()} + valid={form.isValidGender} + attemptedSubmit={form.attemptedSubmit} + invalidWarning={ + 'Custom field can only contain letters and hyphens' + } + width={280} + /> + )} + handleGenderUpdate(value)} + items={[ + {label: 'Male', value: 'male'}, + {label: 'Female', value: 'female'}, + {label: 'Custom', value: 'custom'}, + ]} + placeholder={{ + label: 'Gender', + value: null, + color: '#ddd', + }} + /> + + + + Let's start! + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + height: SCREEN_HEIGHT, + }, + profile: { + marginTop: '10%', + marginBottom: '5%', + }, + contentContainer: { + position: 'relative', + width: 280, + }, + smallProfileUploader: { + justifyContent: 'center', + alignItems: 'center', + padding: 20, + backgroundColor: '#E1F0FF', + height: normalize(150), + width: normalize(150), + borderRadius: normalize(150), + }, + smallProfileText: { + textAlign: 'center', + fontSize: 14, + fontWeight: 'bold', + color: '#806DF4', + }, + smallProfilePic: { + height: normalize(150), + width: normalize(150), + borderRadius: normalize(150), + borderWidth: 2, + borderColor: 'white', + }, + submitBtn: { + backgroundColor: TAGG_PURPLE, + justifyContent: 'center', + alignItems: 'center', + width: SCREEN_WIDTH / 2.5, + height: SCREEN_WIDTH / 10, + borderRadius: 5, + marginTop: '5%', + alignSelf: 'center', + }, + submitBtnLabel: { + fontSize: 16, + fontWeight: '500', + color: '#fff', + }, + goBack: { + textDecorationLine: 'underline', + color: '#fff', + fontSize: 15, + fontWeight: '600', + }, + footer: { + marginTop: '3%', + alignItems: 'center', + justifyContent: 'space-around', + height: SCREEN_HEIGHT * 0.15, + }, + wizard: { + position: 'absolute', + top: SCREEN_HEIGHT * 0.1, + }, + purplePlus: { + position: 'absolute', + height: normalize(40), + width: normalize(40), + bottom: 0, + right: 0, + }, +}); + +export default ProfileInfoOnboarding; diff --git a/src/screens/onboarding/RevampedOnboarding.tsx b/src/screens/onboarding/RevampedOnboarding.tsx deleted file mode 100644 index 782df6a1..00000000 --- a/src/screens/onboarding/RevampedOnboarding.tsx +++ /dev/null @@ -1,458 +0,0 @@ -import { useNavigation } from '@react-navigation/core'; -import { RouteProp } from '@react-navigation/native'; -import { StackNavigationProp } from '@react-navigation/stack'; -import React, { useEffect, useState } from 'react'; -import { - Alert, - KeyboardAvoidingView, - Platform, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { - ArrowButton, - Background, - LoadingIndicator, - TaggInput, - TaggSquareButton, - TermsConditions, -} from '../../components'; -import { - emailRegex, - nameRegex, - passwordRegex, - phoneRegex, - usernameRegex, -} from '../../constants'; -import { - ERROR_NEXT_PAGE, - ERROR_PHONE_IN_USE, - ERROR_TWILIO_SERVER_ERROR, -} from '../../constants/strings'; -import { OnboardingStackParams } from '../../routes'; -import { sendOtpStatusCode } from '../../services'; -import { BackgroundGradientType } from '../../types'; -import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../utils'; - -type RevampedOnboardingRouteProp = RouteProp< - OnboardingStackParams, - 'RevampedOnboarding' ->; -type RevampedOnboardingNavigationProp = StackNavigationProp< - OnboardingStackParams, - 'RevampedOnboarding' ->; -interface RevampedOnboardingProps { - route: RevampedOnboardingRouteProp; - navigation: RevampedOnboardingNavigationProp; -} - -const RevampedOnboarding: React.FC = ({ route }) => { - const { isPhoneVerified } = route.params; - const navigation = useNavigation(); - type renderStatusType = 'firstName' | 'lastName' | 'username' | 'email'; - const [attemptedSubmit, setAttemptedSubmit] = useState(false); - // TODO: maybe use this? - const [valid, setValid] = useState(false); - const [paswordsMatch, setPassMatch] = useState(false) - const [currentStep, setCurrentStep] = useState(0); - const [tcAccepted, setTCAccepted] = useState(false); - const [passVisibility, setPassVisibility] = useState(false); - const [form, setForm] = useState({ - fname: '', - lname: '', - username: '', - phone: '', - email: '', - password: '', - confirm: '', - // isValidFname: false, - // isValidLname: false, - // isValidPhone: false, - // isValidUser: false, - // isValidEmail: false, - // isValidPassword: false, - // passwordsMatch: false, - token: '', - }); - - const goNext = async () => { - if (!attemptedSubmit) { - setAttemptedSubmit(true); - } - try { - if (valid) { - const { phone } = form; - const code = await sendOtpStatusCode(phone); - if (code) { - switch (code) { - case 200: - const { fname, lname } = form; - navigation.navigate('PhoneVerification', { - firstName: fname, - lastName: lname, - phone, - }); - break; - case 409: - Alert.alert(ERROR_PHONE_IN_USE); - break; - default: - Alert.alert(ERROR_TWILIO_SERVER_ERROR); - } - } else { - setAttemptedSubmit(false); - setTimeout(() => { - setAttemptedSubmit(true); - }); - } - } - } catch (error) { - Alert.alert(ERROR_NEXT_PAGE); - return { - name: 'Navigation error', - description: error, - }; - } - }; - // 0 = first name, 1 = last name, 2 = username, 3 = phone # - const handleNameUpdate = (name: string, nameType: number) => { - name = name.trim(); - let isValidName: boolean = nameRegex.test(name); - switch (nameType) { - case 0: - setForm({ - ...form, - fname: name, - }); - setValid(isValidName) - break; - case 1: - setForm({ - ...form, - lname: name, - }); - setValid(isValidName) - break; - case 2: - setForm({ - ...form, - username: name, - }); - setValid(usernameRegex.test(name)) - break; - } - }; - const handlePhoneUpdate = (phone: string) => { - phone = phone.trim(); - setForm({ - ...form, - phone, - }); - setValid(phoneRegex.test(phone)) - }; - const handleEmailUpdate = (email: string) => { - email = email.trim(); - setForm({ - ...form, - email, - }); - setValid(emailRegex.test(email)) - }; - const handlePasswordUpdate = (password: string) => { - setForm({ - ...form, - password, - }); - setValid(passwordRegex.test(password)); - setPassMatch(form.password === form.confirm) - }; - const handleConfirmUpdate = (confirm: string) => { - let passwordsMatch: boolean = form.password === confirm; - setForm({ - ...form, - confirm, - }); - setPassMatch(form.password === form.confirm) - }; - const handleTcUpdate = (tcAccepted: boolean) => { - setTCAccepted(tcAccepted); - }; - const formSteps: { - placeholder: string; - valid: boolean; - onChangeText: (text: string) => void; - }[] = [ - { - placeholder: 'First Name', - valid: valid, - onChangeText: (text) => handleNameUpdate(text, 0), - }, - { - placeholder: 'Last Name', - valid: valid, - onChangeText: (text) => handleNameUpdate(text, 1), - }, - { - placeholder: 'Phone', - valid: valid, - onChangeText: handlePhoneUpdate, - }, - { - placeholder: 'School Email', - valid: valid, - onChangeText: handleEmailUpdate, - }, - { - placeholder: 'Username', - valid: valid, - onChangeText: (text) => handleNameUpdate(text, 2), - }, - { - placeholder: 'Password', - valid: valid, - onChangeText: handlePasswordUpdate, - }, - // ... - ]; - const resetForm = (formStep: String) => { - console.log(step.placeholder) - console.log(valid) - setValid(false); - console.log("after " + step.valid) - switch (formStep) { - case 'First Name': - setForm({ - ...form, - fname: '' - }) - break; - case 'Last Name': - setForm({ - ...form, - lname: '' - }) - break; - case 'Email': - setForm({ - ...form, - email: '' - }) - break; - case 'Password': - setForm({ - ...form, - password: '' - }) - break; - case 'School Email': - setForm({ - ...form, - email: '' - }) - break; - case 'Username': - setForm({ - ...form, - username: '' - }) - break; - } - } - const advance = () => { - setAttemptedSubmit(true); - console.log("valid? before: " + valid) - if (step.valid) { - setCurrentStep(currentStep + 1); - setAttemptedSubmit(false); - setValid(false); - } - console.log("valid? after: " + valid) - } - const togglePassVisibility = (passBool: boolean) => { - setPassVisibility(passBool); - } - const step = formSteps[currentStep]; - - useEffect(() => { - if (isPhoneVerified) { - advance() - } - }, [isPhoneVerified]); - - return ( - - - {/* getting rid of registration progress in onboarding*/} - {/* */} - - - {(currentStep !== 0 && currentStep !== 3) && ( - { - // if I go back do I want to reset the previous form? - setCurrentStep(currentStep - 1); - resetForm(step.placeholder); - setAttemptedSubmit(false); - }} - /> - )} - - {/* { - console.log('fooo'); - setCurrentStep(currentStep - 1); - }} */} - {/* /> */} - {step.placeholder === 'Phone' && !isPhoneVerified ? ( - <> - - - - ) : ( - <> - {step.placeholder !== 'Password' ? ( - <>SIGN UP - - - - ) : ( - <> - togglePassVisibility(!passVisibility)}> - - Show Password - - - - - )} - - )} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - height: SCREEN_HEIGHT, - width: SCREEN_WIDTH, - alignItems: 'center', - justifyContent: 'center', - }, - input: { - width: '100%', - minWidth: '60%', - height: 40, - fontSize: 16, - fontWeight: '600', - color: '#fff', - paddingLeft: 13, - borderBottomWidth: 1, - borderBottomColor: '#fff', - }, - button: { - width: 40, - aspectRatio: 10, - }, - formHeader: { - color: '#fff', - fontSize: 30, - fontWeight: '600', - position: 'absolute', - top: '20%', - }, - load: { - top: '5%', - }, - tc: { - marginVertical: '5%', - }, - footer: { - width: '100%', - flexDirection: 'row', - justifyContent: 'space-around', - ...Platform.select({ - ios: { - bottom: '20%', - }, - android: { - bottom: '10%', - }, - }), - }, -}); -export default RevampedOnboarding; diff --git a/src/screens/onboarding/WelcomeScreen.tsx b/src/screens/onboarding/WelcomeScreen.tsx index 358661f7..f0089206 100644 --- a/src/screens/onboarding/WelcomeScreen.tsx +++ b/src/screens/onboarding/WelcomeScreen.tsx @@ -35,7 +35,7 @@ const WelcomeScreen: React.FC = ({navigation}) => { { - navigation.navigate('RevampedOnboarding', {isPhoneVerified: false}); + navigation.navigate('BasicInfoOnboarding', {isPhoneVerified: false}); }} title={'Next'} buttonStyle={'large'} diff --git a/src/screens/onboarding/index.ts b/src/screens/onboarding/index.ts index c0d99dd2..5423c46e 100644 --- a/src/screens/onboarding/index.ts +++ b/src/screens/onboarding/index.ts @@ -1,12 +1,9 @@ export {default as InvitationCodeVerification} from './InvitationCodeVerification'; export {default as Login} from './Login'; -export {default as OnboardingStepOne} from './OnboardingStepOne'; -export {default as OnboardingStepThree} from './OnboardingStepThree'; -export {default as OnboardingStepTwo} from './OnboardingStepTwo'; +export {default as ProfileInfoOnboarding} from './ProfileInfoOnboarding'; +export {default as BasicInfoOnboarding} from './BasicInfoOnboarding'; export {default as PasswordReset} from './PasswordReset'; export {default as PasswordResetRequest} from './PasswordResetRequest'; export {default as PhoneVerification} from './PhoneVerification'; export {default as PasswordVerification} from './PasswordVerification'; export {default as WelcomeScreen} from './WelcomeScreen'; -export {default as RevampedOnboarding} from './RevampedOnboarding'; - diff --git a/src/screens/onboarding/legacy/OnboardingStepOne.tsx b/src/screens/onboarding/legacy/OnboardingStepOne.tsx new file mode 100644 index 00000000..5e88d004 --- /dev/null +++ b/src/screens/onboarding/legacy/OnboardingStepOne.tsx @@ -0,0 +1,286 @@ +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo, useRef, useState} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { + ArrowButton, + Background, + BasicButton, + // RegistrationWizard, + TaggInput, + TaggSquareButton, +} from '../../components'; +import {nameRegex, phoneRegex} from '../../constants'; +import { + ERROR_NEXT_PAGE, + ERROR_PHONE_IN_USE, + ERROR_TWILIO_SERVER_ERROR, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes'; +import {sendOtpStatusCode} from '../../services'; +import {BackgroundGradientType} from '../../types'; +import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +type OnboardingStepOneNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'OnboardingStepOne' +>; +interface OnboardingStepOneProps { + navigation: OnboardingStepOneNavigationProp; +} + +const OnboardingStepOne: React.FC = ({navigation}) => { + const lnameRef = useRef(); + const emailRef = useRef(); + const phoneRef = useRef(); + + const handleFocusChange = (field: string): void => { + switch (field) { + case 'lname': + const lnameField: any = lnameRef.current; + lnameField.focus(); + break; + case 'email': + const emailField: any = emailRef.current; + emailField.focus(); + break; + case 'phone': + const phoneField: any = phoneRef.current; + phoneField.focus(); + break; + default: + return; + } + }; + + const [form, setForm] = useState({ + fname: '', + lname: '', + phone: '', + isValidFname: false, + isValidLname: false, + isValidPhone: false, + attemptedSubmit: false, + token: '', + }); + + const handleFnameUpdate = (fname: string) => { + fname = fname.trim(); + let isValidFname: boolean = nameRegex.test(fname); + setForm({ + ...form, + fname, + isValidFname, + }); + }; + + const handleLnameUpdate = (lname: string) => { + lname = lname.trim(); + let isValidLname: boolean = nameRegex.test(lname); + setForm({ + ...form, + lname, + isValidLname, + }); + }; + + const handlePhoneUpdate = (phone: string) => { + phone = phone.trim(); + let isValidPhone: boolean = phoneRegex.test(phone); + setForm({ + ...form, + phone, + isValidPhone, + }); + }; + + const goNext = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + const {isValidFname} = form; + // const {isValidFname, isValidLname, isValidPhone} = form; + if (isValidFname) { + const {fname} = form; + navigation.navigate('OnboardingStepTwo', { + firstName: fname, + }); + // const {phone} = form; + // const code = await sendOtpStatusCode(phone); + // if (code) { + // switch (code) { + // case 200: + // const {fname, lname} = form; + + // break; + // case 409: + // Alert.alert(ERROR_PHONE_IN_USE); + // break; + // default: + // Alert.alert(ERROR_TWILIO_SERVER_ERROR); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert(ERROR_NEXT_PAGE); + return { + name: 'Navigation error', + description: error, + }; + } + }; + + const footer = useMemo( + () => ( + + // // + // {/* navigation.navigate('Login')} + // /> */} + // {/* */} + // {/* */} + // {/* */} + // // + ), + [ + form.fname, + // form.lname, + // form.phone, + form.isValidFname, + // form.isValidLname, + // form.isValidPhone, + ], + ); + + return ( + + + {/* getting rid of registration progress in onboarding*/} + {/* */} + + {/* */} + SIGN UP + {/* */} + handleFocusChange('lname')} + blurOnSubmit={false} + valid={form.isValidFname} + invalidWarning="Please enter a valid first name." + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + {/* */} + {/* */} + {footer} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + alignItems: 'center', + justifyContent: 'center', + }, + button: { + width: 40, + aspectRatio: 10, + }, + formHeader: { + color: '#fff', + fontSize: 30, + fontWeight: '600', + marginBottom: '16%', + position: 'absolute', + top: 0, + marginTop: '45%', + }, + load: { + top: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); + +export default OnboardingStepOne; diff --git a/src/screens/onboarding/legacy/OnboardingStepTwo.tsx b/src/screens/onboarding/legacy/OnboardingStepTwo.tsx new file mode 100644 index 00000000..65f953ea --- /dev/null +++ b/src/screens/onboarding/legacy/OnboardingStepTwo.tsx @@ -0,0 +1,368 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo, useRef, useState} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { + Background, + LoadingIndicator, + TaggInput, + TaggSquareButton, + TermsConditions, +} from '../../../components'; +import {emailRegex, passwordRegex, usernameRegex} from '../../../constants'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_REGISTRATION, + ERROR_SOMETHING_WENT_WRONG_REFRESH, +} from '../../../constants/strings'; +import {OnboardingStackParams} from '../../../routes'; +import {sendRegister} from '../../../services'; +import {BackgroundGradientType} from '../../../types'; + +type OnboardingStepTwoRouteProp = RouteProp< + OnboardingStackParams, + 'OnboardingStepTwo' +>; +type OnboardingStepTwoNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'OnboardingStepTwo' +>; +interface OnboardingStepTwoProps { + route: OnboardingStepTwoRouteProp; + navigation: OnboardingStepTwoNavigationProp; +} + +const OnboardingStepTwo: React.FC = ({ + route, + navigation, +}) => { + const emailRef = useRef(); + const usernameRef = useRef(); + const passwordRef = useRef(); + const confirmRef = useRef(); + + const handleFocusChange = (field: string): void => { + switch (field) { + case 'email': + const emailField: any = emailRef.current; + emailField.focus(); + break; + case 'username': + const usernameField: any = usernameRef.current; + usernameField.focus(); + break; + case 'password': + const passwordField: any = passwordRef.current; + passwordField.focus(); + break; + case 'confirm': + const confirmField: any = confirmRef.current; + confirmField.focus(); + break; + default: + return; + } + }; + + // registration form state + const [form, setForm] = useState({ + email: '', + username: '', + password: '', + confirm: '', + isValidEmail: false, + isValidUsername: false, + isValidPassword: false, + passwordsMatch: false, + tcAccepted: false, + attemptedSubmit: false, + }); + + const handleEmailUpdate = (email: string) => { + email = email.trim(); + let isValidEmail: boolean = emailRegex.test(email); + setForm({ + ...form, + email, + isValidEmail, + }); + }; + + const handleUsernameUpdate = (username: string) => { + let isValidUsername: boolean = usernameRegex.test(username); + setForm({ + ...form, + username, + isValidUsername, + }); + }; + + const handlePasswordUpdate = (password: string) => { + let isValidPassword: boolean = passwordRegex.test(password); + let passwordsMatch: boolean = form.password === form.confirm; + setForm({ + ...form, + password, + isValidPassword, + passwordsMatch, + }); + }; + + const handleConfirmUpdate = (confirm: string) => { + let passwordsMatch: boolean = form.password === confirm; + setForm({ + ...form, + confirm, + passwordsMatch, + }); + }; + + const handleTcUpdate = (tcAccepted: boolean) => { + setForm({ + ...form, + tcAccepted, + }); + }; + + const handleRegister = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + const { + isValidEmail, + isValidUsername, + isValidPassword, + passwordsMatch, + tcAccepted, + } = form; + try { + if ( + isValidEmail && + isValidUsername && + isValidPassword && + passwordsMatch + ) { + if (tcAccepted) { + const {email, username, password} = form; + const {firstName, lastName, phone} = route.params; + const response = await sendRegister( + firstName, + lastName, + phone, + email, + username, + password, + ); + if (response) { + const data = await response.json(); + const {token, UserID} = data; + switch (response.status) { + case 201: + await AsyncStorage.setItem('token', token); + navigation.navigate('ProfileInfoOnboarding', { + userId: UserID, + username: username, + }); + break; + case 400: + Alert.alert(ERROR_REGISTRATION(data.toLowerCase())); + break; + default: + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + break; + } + } else { + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + } + } else { + Alert.alert( + 'Terms and conditions', + 'You must first agree to the terms and conditions.', + ); + } + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert(ERROR_REGISTRATION(ERROR_DOUBLE_CHECK_CONNECTION)); + return { + name: 'Registration error', + description: error, + }; + } + }; + + const footer = useMemo( + () => ( + + + + + + ), + [ + form.email, + form.username, + form.password, + form.confirm, + form.isValidEmail, + form.isValidUsername, + form.isValidPassword, + form.passwordsMatch, + form.tcAccepted, + ], + ); + + return ( + + + {/* */} + + + SIGN UP + + handleFocusChange('password')} + blurOnSubmit={false} + ref={usernameRef} + valid={form.isValidUsername} + invalidWarning={ + 'Username must be at least 6 characters and contain only alphanumerics.' + } + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + + handleFocusChange('confirm')} + blurOnSubmit={false} + secureTextEntry + ref={passwordRef} + valid={form.isValidPassword} + invalidWarning={ + 'Password must be at least 8 characters & contain at least one of a-z, A-Z, 0-9, and a special character.' + } + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + + + + + {footer} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + // wizard: { + // position: 'absolute', + // top: SCREEN_HEIGHT * 0.1, + // }, + formHeader: { + color: '#fff', + fontSize: 30, + fontWeight: '600', + marginBottom: '16%', + }, + tc: { + marginVertical: '5%', + }, + load: { + top: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); + +export default OnboardingStepTwo; -- cgit v1.2.3-70-g09d2