diff options
author | Ivan Chen <ivan@tagg.id> | 2021-02-26 21:16:02 -0500 |
---|---|---|
committer | Ivan Chen <ivan@tagg.id> | 2021-02-26 21:16:02 -0500 |
commit | 7faeb487da4fac1e57d8d147da1e41cac16bb28d (patch) | |
tree | 6e49e419200c670a8de1dd04c67a251acac97a1c /src | |
parent | bd3e189405e013b847b74fbad66543f6368a4ec8 (diff) |
onboarding revamp done!
Diffstat (limited to 'src')
-rw-r--r-- | src/screens/onboarding/InvitationCodeVerification.tsx | 115 | ||||
-rw-r--r-- | src/screens/onboarding/Login.tsx | 15 | ||||
-rw-r--r-- | src/screens/onboarding/OnboardingStepOne.tsx | 263 | ||||
-rw-r--r-- | src/screens/onboarding/OnboardingStepThree.tsx | 411 | ||||
-rw-r--r-- | src/screens/onboarding/OnboardingStepTwo.tsx | 369 | ||||
-rw-r--r-- | src/screens/onboarding/PasswordReset.tsx | 1 | ||||
-rw-r--r-- | src/screens/onboarding/PasswordResetRequest.tsx | 29 | ||||
-rw-r--r-- | src/screens/onboarding/PhoneVerification.tsx | 225 | ||||
-rw-r--r-- | src/screens/onboarding/Verification.tsx | 51 | ||||
-rw-r--r-- | src/screens/onboarding/WelcomeScreen.tsx | 7 | ||||
-rw-r--r-- | src/screens/onboarding/index.ts | 4 | ||||
-rw-r--r-- | src/services/UserProfileService.ts | 45 |
12 files changed, 1413 insertions, 122 deletions
diff --git a/src/screens/onboarding/InvitationCodeVerification.tsx b/src/screens/onboarding/InvitationCodeVerification.tsx index 903a9912..41d17f29 100644 --- a/src/screens/onboarding/InvitationCodeVerification.tsx +++ b/src/screens/onboarding/InvitationCodeVerification.tsx @@ -1,20 +1,7 @@ -import React from 'react'; -import {OnboardingStackParams} from '../../routes'; +import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; - -import { - Background, - RegistrationWizard, - SubmitButton, - ArrowButton, - LoadingIndicator, -} from '../../components'; - -import { - TAGG_LIGHT_PURPLE, - VERIFY_INVITATION_CODE_ENDPOUNT, -} from '../../constants'; - +import React from 'react'; +import {Alert, KeyboardAvoidingView, StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import { CodeField, @@ -23,28 +10,35 @@ import { useClearByFocusCell, } from 'react-native-confirmation-code-field'; import { - StyleSheet, - View, - KeyboardAvoidingView, - Alert, - Platform, -} from 'react-native'; - -import {BackgroundGradientType} from '../../types'; + ArrowButton, + Background, + LoadingIndicator, + SubmitButton, +} from '../../components'; +import {VERIFY_INVITATION_CODE_ENDPOUNT} from '../../constants'; import { ERROR_DOUBLE_CHECK_CONNECTION, ERROR_INVALID_INVITATION_CODE, ERROR_INVLAID_CODE, ERROR_VERIFICATION_FAILED_SHORT, + SUCCESS_INVITATION_CODE, } from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes'; +import {BackgroundGradientType} from '../../types'; +import {SCREEN_WIDTH} from '../../utils'; -type InvitationCodeVerificationScreenNavigationProp = StackNavigationProp< +type InvitationCodeVerificationRouteProp = RouteProp< + OnboardingStackParams, + 'InvitationCodeVerification' +>; +type InvitationCodeVerificationNavigationProp = StackNavigationProp< OnboardingStackParams, 'InvitationCodeVerification' >; interface InvitationCodeVerificationProps { - navigation: InvitationCodeVerificationScreenNavigationProp; + navigation: InvitationCodeVerificationNavigationProp; + route: InvitationCodeVerificationRouteProp; } /** @@ -53,6 +47,7 @@ interface InvitationCodeVerificationProps { */ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ + route, navigation, }) => { const [value, setValue] = React.useState(''); @@ -66,19 +61,28 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ if (value.length === 6) { try { let verifyInviteCodeResponse = await fetch( - VERIFY_INVITATION_CODE_ENDPOUNT + value + '/', + VERIFY_INVITATION_CODE_ENDPOUNT + + value + + '/?user_id=' + + route.params.userId, { method: 'DELETE', }, ); if (verifyInviteCodeResponse.status === 200) { - navigation.navigate('RegistrationOne'); + navigation.navigate('Login'); + setTimeout(() => { + Alert.alert(SUCCESS_INVITATION_CODE); + }, 500); } else { Alert.alert(ERROR_INVALID_INVITATION_CODE); } } catch (error) { - Alert.alert(ERROR_VERIFICATION_FAILED_SHORT, ERROR_DOUBLE_CHECK_CONNECTION); + Alert.alert( + ERROR_VERIFICATION_FAILED_SHORT, + ERROR_DOUBLE_CHECK_CONNECTION, + ); return { name: 'Verification error', description: error, @@ -89,10 +93,6 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ } }; - const navigateToAddWaitList = () => { - navigation.navigate('AddWaitlistUser'); - }; - const Footer = () => ( <View style={styles.footer}> <ArrowButton @@ -107,13 +107,8 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ centered style={styles.container} gradientType={BackgroundGradientType.Light}> - <RegistrationWizard style={styles.wizard} step="one" /> <KeyboardAvoidingView behavior="padding" style={styles.form}> - <Text style={styles.formHeader}>Enter the code</Text> - <Text style={styles.description}> - Please enter the invitation code provided to you by us / your friend. - (Use all caps.) - </Text> + <Text style={styles.formHeader}>Enter Your Invitation Code</Text> <CodeField ref={ref} {...valueProps} @@ -141,13 +136,10 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ accessibilityHint="Select this after entering your invitation code" onPress={handleInvitationCodeVerification} /> - <View style={styles.noInviteCode}> - <Text style={styles.inviteCodeText}>Don't have an invite? </Text> - <Text style={styles.inviteCodeLink} onPress={navigateToAddWaitList}> - {' '} - Join the Waitlist - </Text> - </View> + <Text style={styles.youveBeenAddedLabel}> + You've been added to the waitlist! We'll notify you when you're at the + front of the line! + </Text> <LoadingIndicator /> </KeyboardAvoidingView> <Footer /> @@ -160,29 +152,17 @@ const styles = StyleSheet.create({ flex: 1, alignItems: 'center', justifyContent: 'center', - }, - wizard: { - marginTop: '3.5%', - flex: 1, - justifyContent: 'center', + borderWidth: 1, }, form: { alignItems: 'center', justifyContent: 'flex-start', - flex: 3, }, formHeader: { color: '#fff', fontSize: 20, fontWeight: 'bold', alignSelf: 'flex-start', - marginBottom: '6%', - marginHorizontal: '10%', - }, - description: { - color: '#fff', - fontWeight: '600', - fontSize: 17, marginHorizontal: '10%', }, codeFieldRoot: { @@ -214,22 +194,19 @@ const styles = StyleSheet.create({ width: '100%', flexDirection: 'row', justifyContent: 'space-around', - ...Platform.select({ - ios: { - bottom: '20%', - }, - android: { - bottom: '10%', - }, - }), }, noInviteCode: { flexDirection: 'row', justifyContent: 'center', }, - inviteCodeText: { - color: TAGG_LIGHT_PURPLE, + youveBeenAddedLabel: { + marginVertical: '5%', + width: SCREEN_WIDTH * 0.8, + color: 'white', + textAlign: 'center', fontSize: 18, + fontWeight: '500', + marginBottom: '10%', }, inviteCodeLink: { color: 'white', diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 450c5039..2ca4172b 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -1,7 +1,7 @@ import AsyncStorage from '@react-native-community/async-storage'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useEffect, useRef, useState} from 'react'; +import React, {useEffect, useRef} from 'react'; import { Alert, Image, @@ -21,12 +21,13 @@ import { ERROR_FAILED_LOGIN_INFO, ERROR_INVALID_LOGIN, ERROR_LOGIN_FAILED, + ERROR_NOT_ONBOARDED, ERROR_SOMETHING_WENT_WRONG_REFRESH, } from '../../constants/strings'; import {OnboardingStackParams} from '../../routes/onboarding'; import {fcmService} from '../../services'; import {RootState} from '../../store/rootReducer'; -import {BackgroundGradientType, UserType} from '../../types'; +import {BackgroundGradientType} from '../../types'; import {normalize, userLogin} from '../../utils'; import UpdateRequired from './UpdateRequired'; @@ -155,7 +156,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { let statusCode = response.status; let data = await response.json(); - if (statusCode === 200) { + if (statusCode === 200 && data.isOnboarded) { //Stores token received in the response into client's AsynStorage try { await AsyncStorage.setItem('token', data.token); @@ -167,6 +168,13 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { console.log(data); Alert.alert(ERROR_INVALID_LOGIN); } + } else if (statusCode === 200 && !data.isOnboarded) { + navigation.navigate('InvitationCodeVerification', { + userId: data.UserID, + }); + setTimeout(() => { + Alert.alert(ERROR_NOT_ONBOARDED); + }, 500); } else if (statusCode === 401) { Alert.alert(ERROR_FAILED_LOGIN_INFO); } else { @@ -192,7 +200,6 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { navigation.navigate('WelcomeScreen'); setForm({...form, attemptedSubmit: false}); }; - /** * Login screen forgot password button. */ diff --git a/src/screens/onboarding/OnboardingStepOne.tsx b/src/screens/onboarding/OnboardingStepOne.tsx new file mode 100644 index 00000000..0fa7a6a5 --- /dev/null +++ b/src/screens/onboarding/OnboardingStepOne.tsx @@ -0,0 +1,263 @@ +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo, useRef, useState} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { + ArrowButton, + Background, + RegistrationWizard, + TaggInput, +} from '../../components'; +import {nameRegex, phoneRegex} from '../../constants'; +import { + ERROR_NEXT_PAGE, + ERROR_PHONE_IN_USE, + ERROR_TWILIO_SERVER_ERROR, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes'; +import {sendOtpStatusCode} from '../../services'; +import {BackgroundGradientType} from '../../types'; +import {SCREEN_HEIGHT} from '../../utils'; + +type OnboardingStepOneNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'OnboardingStepOne' +>; +interface OnboardingStepOneProps { + navigation: OnboardingStepOneNavigationProp; +} + +const OnboardingStepOne: React.FC<OnboardingStepOneProps> = ({navigation}) => { + const lnameRef = useRef(); + const emailRef = useRef(); + const phoneRef = useRef(); + + const handleFocusChange = (field: string): void => { + switch (field) { + case 'lname': + const lnameField: any = lnameRef.current; + lnameField.focus(); + break; + case 'email': + const emailField: any = emailRef.current; + emailField.focus(); + break; + case 'phone': + const phoneField: any = phoneRef.current; + phoneField.focus(); + break; + default: + return; + } + }; + + const [form, setForm] = useState({ + fname: '', + lname: '', + phone: '', + isValidFname: false, + isValidLname: false, + isValidPhone: false, + attemptedSubmit: false, + token: '', + }); + + const handleFnameUpdate = (fname: string) => { + fname = fname.trim(); + let isValidFname: boolean = nameRegex.test(fname); + setForm({ + ...form, + fname, + isValidFname, + }); + }; + + const handleLnameUpdate = (lname: string) => { + lname = lname.trim(); + let isValidLname: boolean = nameRegex.test(lname); + setForm({ + ...form, + lname, + isValidLname, + }); + }; + + const handlePhoneUpdate = (phone: string) => { + phone = phone.trim(); + let isValidPhone: boolean = phoneRegex.test(phone); + setForm({ + ...form, + phone, + isValidPhone, + }); + }; + + const goNext = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + if (form.isValidFname && form.isValidLname && form.isValidPhone) { + const code = await sendOtpStatusCode(form.phone); + if (code) { + switch (code) { + case 200: + navigation.navigate('PhoneVerification', { + firstName: form.fname, + lastName: form.lname, + phone: form.phone, + }); + break; + case 409: + Alert.alert(ERROR_PHONE_IN_USE); + break; + default: + Alert.alert(ERROR_TWILIO_SERVER_ERROR); + } + } + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert(ERROR_NEXT_PAGE); + return { + name: 'Navigation error', + description: error, + }; + } + }; + + const footer = useMemo( + () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => navigation.navigate('Login')} + /> + <TouchableOpacity onPress={goNext}> + <ArrowButton + direction="forward" + disabled={ + !(form.isValidFname && form.isValidLname && form.isValidPhone) + } + onPress={goNext} + /> + </TouchableOpacity> + </View> + ), + [form.isValidFname, form.isValidLname, form.isValidPhone], + ); + + return ( + <Background + style={styles.container} + gradientType={BackgroundGradientType.Light}> + <StatusBar barStyle="light-content" /> + <RegistrationWizard style={styles.wizard} step="one" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <View> + <Text style={styles.formHeader}>ENTER NAME</Text> + </View> + <TaggInput + accessibilityHint="Enter your first name." + accessibilityLabel="First name input field." + placeholder="First Name" + autoCompleteType="name" + textContentType="name" + returnKeyType="next" + onChangeText={handleFnameUpdate} + onSubmitEditing={() => handleFocusChange('lname')} + blurOnSubmit={false} + valid={form.isValidFname} + invalidWarning="Please enter a valid first name." + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + accessibilityHint="Enter your last name." + accessibilityLabel="Last name input field." + placeholder="Last Name" + autoCompleteType="name" + textContentType="name" + returnKeyType="next" + onChangeText={handleLnameUpdate} + blurOnSubmit={false} + ref={lnameRef} + valid={form.isValidLname} + invalidWarning="Please enter a valid last name." + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + maxLength={10} // currently only support US phone numbers + accessibilityHint="Enter your phone number." + accessibilityLabel="Phone number input field." + placeholder="Phone Number" + autoCompleteType="tel" + textContentType="telephoneNumber" + autoCapitalize="none" + keyboardType="number-pad" + onChangeText={handlePhoneUpdate} + blurOnSubmit={false} + ref={phoneRef} + valid={form.isValidPhone} + invalidWarning={'Please enter a valid 10 digit number.'} + attemptedSubmit={form.attemptedSubmit} + width={280} + onSubmitEditing={goNext} + /> + </KeyboardAvoidingView> + {footer} + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + wizard: { + position: 'absolute', + top: SCREEN_HEIGHT * 0.1, + }, + formHeader: { + color: '#fff', + fontSize: 30, + fontWeight: '600', + marginBottom: '16%', + }, + load: { + top: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); + +export default OnboardingStepOne; diff --git a/src/screens/onboarding/OnboardingStepThree.tsx b/src/screens/onboarding/OnboardingStepThree.tsx new file mode 100644 index 00000000..f832539d --- /dev/null +++ b/src/screens/onboarding/OnboardingStepThree.tsx @@ -0,0 +1,411 @@ +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, + RegistrationWizard, + TaggDropDown, + TaggInput, +} from '../../components'; +import { + CLASS_YEAR_LIST, + EDIT_PROFILE_ENDPOINT, + genderRegex, + TAGG_PURPLE, +} from '../../constants'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_PROFILE_CREATION_SHORT, + ERROR_SELECT_BIRTHDAY, + ERROR_SELECT_CLASS_YEAR, + ERROR_SELECT_GENDER, + ERROR_SOMETHING_WENT_WRONG_REFRESH, + ERROR_UPLOAD_SMALL_PROFILE_PIC, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes/onboarding'; +import {BackgroundGradientType} 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; +} + +const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({ + route, + navigation, +}) => { + const {userId, username} = route.params; + let emptyDate: string | undefined; + const [form, setForm] = React.useState({ + smallPic: '', + birthdate: emptyDate, + 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 = () => ( + <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 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) => { + console.log('foooooo'); + const classYear = Number.parseInt(value); + 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.smallPic) { + Alert.alert(ERROR_UPLOAD_SMALL_PROFILE_PIC); + return; + } + if (form.classYear === -1) { + Alert.alert(ERROR_SELECT_CLASS_YEAR); + return; + } + if (form.birthdate === emptyDate) { + Alert.alert(ERROR_SELECT_BIRTHDAY); + return; + } + if (form.gender === '') { + Alert.alert(ERROR_SELECT_GENDER); + return; + } + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + let invalidFields: boolean = false; + const request = new FormData(); + if (form.smallPic) { + request.append('smallProfilePicture', { + uri: form.smallPic, + name: 'small_profile_pic.jpg', + type: 'image/jpg', + }); + } + + 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, + }); + console.log(route.params.userId); + let statusCode = response.status; + let data = await response.json(); + if (statusCode === 200) { + navigation.navigate('InvitationCodeVerification', { + userId: route.params.userId, + }); + } 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}> + <SmallProfilePic /> + <Image + source={require('../../assets/icons/purple-plus.png')} + style={styles.purplePlus} + /> + </View> + ); + }, [form.largePic, form.smallPic]); + + return ( + <Animated.ScrollView bounces={false}> + <Background + centered + gradientType={BackgroundGradientType.Light} + style={styles.container}> + <StatusBar barStyle="light-content" /> + <RegistrationWizard style={styles.wizard} step="three" /> + {profilePics} + <View style={styles.contentContainer}> + <TaggDropDown + onValueChange={(value: string) => handleClassYearUpdate(value)} + items={classYearList} + placeholder={{ + label: 'Class Year', + value: null, + color: '#ddd', + }} + /> + <BirthDatePicker + handleBDUpdate={handleBirthdateUpdate} + width={280} + date={form.birthdate} + showPresetdate={false} + /> + {customGender && ( + <TaggInput + accessibilityHint="Custom" + accessibilityLabel="Gender input field." + placeholder="Enter your gender" + autoCompleteType="off" + textContentType="none" + autoCapitalize="none" + returnKeyType="next" + blurOnSubmit={false} + 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', + }} + /> + </View> + <View style={styles.footer}> + <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: '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 new file mode 100644 index 00000000..de869c99 --- /dev/null +++ b/src/screens/onboarding/OnboardingStepTwo.tsx @@ -0,0 +1,369 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo, useRef, useState} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { + ArrowButton, + Background, + LoadingIndicator, + RegistrationWizard, + TaggInput, + TermsConditions, +} from '../../components'; +import {emailRegex, passwordRegex, usernameRegex} from '../../constants'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_REGISTRATION, + ERROR_SOMETHING_WENT_WRONG_REFRESH, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes'; +import {sendRegister} from '../../services'; +import {BackgroundGradientType} from '../../types'; +import {SCREEN_HEIGHT} from '../../utils'; + +type OnboardingStepTwoRouteProp = RouteProp< + OnboardingStackParams, + 'OnboardingStepTwo' +>; +type OnboardingStepTwoNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'OnboardingStepTwo' +>; +interface OnboardingStepTwoProps { + route: OnboardingStepTwoRouteProp; + navigation: OnboardingStepTwoNavigationProp; +} + +const OnboardingStepTwo: React.FC<OnboardingStepTwoProps> = ({ + route, + navigation, +}) => { + const emailRef = useRef(); + const usernameRef = useRef(); + const passwordRef = useRef(); + const confirmRef = useRef(); + + const handleFocusChange = (field: string): void => { + switch (field) { + case 'email': + const emailField: any = emailRef.current; + emailField.focus(); + break; + case 'username': + const usernameField: any = usernameRef.current; + usernameField.focus(); + break; + case 'password': + const passwordField: any = passwordRef.current; + passwordField.focus(); + break; + case 'confirm': + const confirmField: any = confirmRef.current; + confirmField.focus(); + break; + default: + return; + } + }; + + // registration form state + const [form, setForm] = useState({ + email: '', + username: '', + password: '', + confirm: '', + isValidEmail: false, + isValidUsername: false, + isValidPassword: false, + passwordsMatch: false, + tcAccepted: false, + attemptedSubmit: false, + }); + + const handleEmailUpdate = (email: string) => { + email = email.trim(); + let isValidEmail: boolean = emailRegex.test(email); + setForm({ + ...form, + email, + isValidEmail, + }); + }; + + const handleUsernameUpdate = (username: string) => { + let isValidUsername: boolean = usernameRegex.test(username); + setForm({ + ...form, + username, + isValidUsername, + }); + }; + + const handlePasswordUpdate = (password: string) => { + let isValidPassword: boolean = passwordRegex.test(password); + let passwordsMatch: boolean = form.password === form.confirm; + setForm({ + ...form, + password, + isValidPassword, + passwordsMatch, + }); + }; + + const handleConfirmUpdate = (confirm: string) => { + let passwordsMatch: boolean = form.password === confirm; + setForm({ + ...form, + confirm, + passwordsMatch, + }); + }; + + const handleTcUpdate = (tcAccepted: boolean) => { + setForm({ + ...form, + tcAccepted, + }); + }; + + const handleRegister = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + if ( + form.isValidEmail && + form.isValidUsername && + form.isValidPassword && + form.passwordsMatch + ) { + if (form.tcAccepted) { + const response = await sendRegister( + route.params.firstName, + route.params.lastName, + route.params.phone, + form.email, + form.username, + form.password, + ); + if (response) { + const data = await response.json(); + switch (response.status) { + case 201: + await AsyncStorage.setItem('token', data.token); + navigation.navigate('OnboardingStepThree', { + userId: data.UserID, + username: form.username, + }); + break; + case 400: + Alert.alert(ERROR_REGISTRATION(Object.values(data))); + break; + default: + console.log('fooo'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + break; + } + } else { + console.log('barrr'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + } + } else { + Alert.alert( + 'Terms and conditions', + 'You must first agree to the terms and conditions.', + ); + } + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert(ERROR_REGISTRATION(ERROR_DOUBLE_CHECK_CONNECTION)); + return { + name: 'Registration error', + description: error, + }; + } + }; + + const footer = useMemo( + () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => + navigation.navigate('PhoneVerification', {...route.params}) + } + /> + <TouchableOpacity onPress={handleRegister}> + <ArrowButton + direction="forward" + disabled={ + !( + form.isValidUsername && + form.isValidPassword && + form.passwordsMatch && + form.tcAccepted + ) + } + onPress={handleRegister} + /> + </TouchableOpacity> + </View> + ), + [ + form.isValidEmail, + form.isValidUsername, + form.isValidPassword, + form.passwordsMatch, + form.tcAccepted, + ], + ); + + return ( + <Background + style={styles.container} + gradientType={BackgroundGradientType.Light}> + <StatusBar barStyle="light-content" /> + <RegistrationWizard style={styles.wizard} step="two" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <View> + <Text style={styles.formHeader}>SIGN UP</Text> + </View> + <TaggInput + accessibilityHint="Enter your email." + accessibilityLabel="Email input field." + placeholder="Email" + autoCompleteType="email" + textContentType="emailAddress" + autoCapitalize="none" + returnKeyType="next" + keyboardType="email-address" + onChangeText={handleEmailUpdate} + blurOnSubmit={false} + ref={emailRef} + valid={form.isValidEmail} + invalidWarning={'Please enter a valid email address.'} + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + accessibilityHint="Enter a 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: { + 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/PasswordReset.tsx b/src/screens/onboarding/PasswordReset.tsx index 11ca60d5..fab77b72 100644 --- a/src/screens/onboarding/PasswordReset.tsx +++ b/src/screens/onboarding/PasswordReset.tsx @@ -227,6 +227,7 @@ const styles = StyleSheet.create({ fontWeight: '600', fontSize: 17, marginHorizontal: '10%', + marginBottom: '10%', }, footer: { width: '100%', diff --git a/src/screens/onboarding/PasswordResetRequest.tsx b/src/screens/onboarding/PasswordResetRequest.tsx index cf086f59..a63eae81 100644 --- a/src/screens/onboarding/PasswordResetRequest.tsx +++ b/src/screens/onboarding/PasswordResetRequest.tsx @@ -1,28 +1,25 @@ -import React, {useState, useRef} from 'react'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useState} from 'react'; import { - View, - Text, - StyleSheet, - StatusBar, Alert, + KeyboardAvoidingView, Platform, + StatusBar, + StyleSheet, + Text, TouchableOpacity, - KeyboardAvoidingView, + View, } from 'react-native'; - -import {OnboardingStackParams} from '../../routes'; - +import {trackPromise} from 'react-promise-tracker'; import { ArrowButton, - TaggInput, Background, LoadingIndicator, + TaggInput, } from '../../components'; - -import {trackPromise} from 'react-promise-tracker'; import {emailRegex, usernameRegex} from '../../constants'; +import {OnboardingStackParams} from '../../routes'; import {handlePasswordResetRequest} from '../../services'; import {BackgroundGradientType, VerificationScreenType} from '../../types'; @@ -123,14 +120,12 @@ const PasswordResetRequest: React.FC<PasswordResetRequestProps> = ({ behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.container}> <View> - <Text style={styles.description}> - Enter your registered username / email - </Text> + <Text style={styles.description}>Enter your registered username</Text> </View> <TaggInput - accessibilityHint="Enter a username / email" + accessibilityHint="Enter a username" accessibilityLabel="Input field." - placeholder="Username / Email" + placeholder="Username" autoCompleteType="username" textContentType="username" autoCapitalize="none" diff --git a/src/screens/onboarding/PhoneVerification.tsx b/src/screens/onboarding/PhoneVerification.tsx new file mode 100644 index 00000000..6ec511b3 --- /dev/null +++ b/src/screens/onboarding/PhoneVerification.tsx @@ -0,0 +1,225 @@ +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useMemo} from 'react'; +import { + Alert, + KeyboardAvoidingView, + Platform, + StyleSheet, + TouchableOpacity, + View, +} from 'react-native'; +import {Text} from 'react-native-animatable'; +import { + CodeField, + Cursor, + useBlurOnFulfill, + useClearByFocusCell, +} from 'react-native-confirmation-code-field'; +import {trackPromise} from 'react-promise-tracker'; +import { + ArrowButton, + Background, + LoadingIndicator, + RegistrationWizard, + SubmitButton, +} from '../../components'; +import {codeRegex} from '../../constants'; +import { + ERROR_INVALID_VERIFICATION_CODE_FORMAT, + ERROR_SOMETHING_WENT_WRONG, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes'; +import {sendOtp, verifyOtp} from '../../services'; +import {BackgroundGradientType} from '../../types'; +import {SCREEN_HEIGHT} from '../../utils'; + +type PhoneVerificationRouteProp = RouteProp< + OnboardingStackParams, + 'PhoneVerification' +>; +type PhoneVerificationNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'PhoneVerification' +>; +interface PhoneVerificationProps { + route: PhoneVerificationRouteProp; + navigation: PhoneVerificationNavigationProp; +} + +const PhoneVerification: React.FC<PhoneVerificationProps> = ({ + route, + navigation, +}) => { + const [value, setValue] = React.useState(''); + const ref = useBlurOnFulfill({value, cellCount: 6}); + const [valueProps, getCellOnLayoutHandler] = useClearByFocusCell({ + value, + setValue, + }); + const {phone} = route.params; + + const handleVerification = async () => { + if (!codeRegex.test(value)) { + Alert.alert(ERROR_INVALID_VERIFICATION_CODE_FORMAT); + return; + } + try { + const success = await trackPromise(verifyOtp(phone, value)); + if (success) { + navigation.navigate('OnboardingStepTwo', { + ...route.params, + }); + } + } catch (error) { + console.log(error); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); + } + }; + + const footer = useMemo( + () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => navigation.navigate('OnboardingStepOne')} + /> + </View> + ), + [], + ); + + return ( + <Background + centered + style={styles.container} + gradientType={BackgroundGradientType.Light}> + <RegistrationWizard style={styles.wizard} step="one" /> + <KeyboardAvoidingView behavior="padding" style={styles.form}> + <Text style={styles.formHeader}>Enter 6 digit code</Text> + <Text style={styles.description}> + We sent a 6 digit verification code to the phone number you provided. + </Text> + <CodeField + ref={ref} + {...valueProps} + value={value} + onChangeText={setValue} + cellCount={6} + rootStyle={styles.codeFieldRoot} + keyboardType="number-pad" + textContentType="oneTimeCode" + renderCell={({index, symbol, isFocused}) => ( + <View + onLayout={getCellOnLayoutHandler(index)} + key={index} + style={[styles.cellRoot, isFocused && styles.focusCell]}> + <Text style={styles.cellText}> + {symbol || (isFocused ? <Cursor /> : null)} + </Text> + </View> + )} + /> + <SubmitButton + text="Verify" + color="#fff" + style={styles.button} + accessibilityLabel="Verify" + accessibilityHint="Select this after entering your phone number verification code" + onPress={handleVerification} + /> + <TouchableOpacity onPress={() => sendOtp(phone)}> + <Text style={styles.resend}>Resend Code</Text> + </TouchableOpacity> + <LoadingIndicator /> + </KeyboardAvoidingView> + {footer} + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + wizard: { + position: 'absolute', + top: SCREEN_HEIGHT * 0.1, + }, + form: { + top: '20%', + alignItems: 'center', + justifyContent: 'flex-start', + flex: 3, + }, + formPasswordVerification: { + alignItems: 'center', + justifyContent: 'flex-start', + flex: 3, + top: '35%', + }, + formHeader: { + color: '#fff', + fontSize: 20, + fontWeight: 'bold', + alignSelf: 'flex-start', + marginBottom: '6%', + marginHorizontal: '10%', + }, + description: { + color: '#fff', + fontWeight: '600', + fontSize: 17, + marginHorizontal: '10%', + }, + resend: { + textDecorationLine: 'underline', + color: '#fff', + fontSize: 15, + fontWeight: '600', + }, + codeFieldRoot: { + width: 280, + marginHorizontal: 'auto', + marginVertical: '15%', + }, + cellRoot: { + width: 40, + height: 60, + justifyContent: 'center', + alignItems: 'center', + borderBottomColor: '#fff', + borderBottomWidth: 1, + }, + cellText: { + color: '#fff', + fontSize: 48, + textAlign: 'center', + }, + focusCell: { + borderBottomColor: '#78a0ef', + borderBottomWidth: 2, + }, + button: { + marginVertical: '5%', + }, + loadingIndicator: { + marginVertical: '5%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); +export default PhoneVerification; diff --git a/src/screens/onboarding/Verification.tsx b/src/screens/onboarding/Verification.tsx index 0fbe0d91..dda18364 100644 --- a/src/screens/onboarding/Verification.tsx +++ b/src/screens/onboarding/Verification.tsx @@ -1,16 +1,14 @@ -import React from 'react'; - -import {OnboardingStackParams} from '../../routes'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; +import React from 'react'; import { - Background, - RegistrationWizard, - SubmitButton, - ArrowButton, - LoadingIndicator, -} from '../../components'; - + Alert, + KeyboardAvoidingView, + Platform, + StyleSheet, + TouchableOpacity, + View, +} from 'react-native'; import {Text} from 'react-native-animatable'; import { CodeField, @@ -18,22 +16,27 @@ import { useBlurOnFulfill, useClearByFocusCell, } from 'react-native-confirmation-code-field'; -import { - StyleSheet, - View, - TouchableOpacity, - KeyboardAvoidingView, - Alert, - Platform, -} from 'react-native'; import {trackPromise} from 'react-promise-tracker'; -import {BackgroundGradientType, VerificationScreenType} from '../../types'; +import { + ArrowButton, + Background, + LoadingIndicator, + RegistrationWizard, + SubmitButton, +} from '../../components'; +import {codeRegex} from '../../constants'; +import { + ERROR_INVALID_VERIFICATION_CODE_FORMAT, + ERROR_SOMETHING_WENT_WRONG, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes'; import { handlePasswordCodeVerification, + handlePasswordResetRequest, sendOtp, verifyOtp, - handlePasswordResetRequest, } from '../../services'; +import {BackgroundGradientType, VerificationScreenType} from '../../types'; type VerificationScreenRouteProp = RouteProp< OnboardingStackParams, @@ -48,12 +51,6 @@ interface VerificationProps { navigation: VerificationScreenNavigationProp; } -import {codeRegex} from '../../constants'; -import { - ERROR_INVALID_VERIFICATION_CODE_FORMAT, - ERROR_SOMETHING_WENT_WRONG, -} from '../../constants/strings'; - const Verification: React.FC<VerificationProps> = ({route, navigation}) => { const [value, setValue] = React.useState(''); const ref = useBlurOnFulfill({value, cellCount: 6}); @@ -217,7 +214,7 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'flex-start', flex: 3, - top: '35%', + top: '25%', }, formHeader: { color: '#fff', diff --git a/src/screens/onboarding/WelcomeScreen.tsx b/src/screens/onboarding/WelcomeScreen.tsx index ae31f933..c36a6e05 100644 --- a/src/screens/onboarding/WelcomeScreen.tsx +++ b/src/screens/onboarding/WelcomeScreen.tsx @@ -16,9 +16,6 @@ interface WelcomeScreenProps { } const WelcomeScreen: React.FC<WelcomeScreenProps> = ({navigation}) => { - const handleNext = () => { - navigation.navigate('InvitationCodeVerification'); - }; return ( <Background style={styles.container} @@ -37,7 +34,9 @@ const WelcomeScreen: React.FC<WelcomeScreenProps> = ({navigation}) => { </Text> </View> <TaggSquareButton - onPress={handleNext} + onPress={() => { + navigation.navigate('OnboardingStepOne'); + }} title={'Next'} buttonStyle={'large'} buttonColor={'purple'} diff --git a/src/screens/onboarding/index.ts b/src/screens/onboarding/index.ts index 596683e5..49d7cfb9 100644 --- a/src/screens/onboarding/index.ts +++ b/src/screens/onboarding/index.ts @@ -15,3 +15,7 @@ export {default as AddWaitlistUserScreen} from './AddWaitlistUserScreen'; export {default as WaitlistSuccessScreen} from './WaitlistSuccessScreen'; export {default as CreateCustomCategory} from './CreateCustomCategory'; export {default as UpdateRequired} from './UpdateRequired'; +export {default as OnboardingStepOne} from './OnboardingStepOne'; +export {default as PhoneVerification} from './PhoneVerification'; +export {default as OnboardingStepTwo} from './OnboardingStepTwo'; +export {default as OnboardingStepThree} from './OnboardingStepThree'; diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index bfc4933f..dd77db9f 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -11,6 +11,7 @@ import { PROFILE_INFO_ENDPOINT, PROFILE_PHOTO_ENDPOINT, PROFILE_PHOTO_THUMBNAIL_ENDPOINT, + REGISTER_ENDPOINT, SEND_OTP_ENDPOINT, TAGG_CUSTOMER_SUPPORT, VERIFY_OTP_ENDPOINT, @@ -292,7 +293,6 @@ export const verifyOtp = async (phone: string, otp: string) => { export const sendOtp = async (phone: string) => { try { - console.log(phone); let response = await fetch(SEND_OTP_ENDPOINT, { method: 'POST', body: JSON.stringify({ @@ -313,3 +313,46 @@ export const sendOtp = async (phone: string) => { return false; } }; + +export const sendOtpStatusCode = async (phone: string) => { + try { + let response = await fetch(SEND_OTP_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + phone_number: '+1' + phone, + }), + }); + + return response.status; + } catch (error) { + console.log(error); + return undefined; + } +}; + +export const sendRegister = async ( + firstName: string, + lastName: string, + phone: string, + email: string, + username: string, + password: string, +) => { + try { + const response = await fetch(REGISTER_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + first_name: firstName, + last_name: lastName, + email: email, + phone_number: phone, + username: username, + password: password, + }), + }); + return response; + } catch (error) { + console.log(error); + return undefined; + } +}; |