diff options
Diffstat (limited to 'src/screens/onboarding/ProfileOnboarding.tsx')
-rw-r--r-- | src/screens/onboarding/ProfileOnboarding.tsx | 310 |
1 files changed, 272 insertions, 38 deletions
diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx index 9405ca52..ea045434 100644 --- a/src/screens/onboarding/ProfileOnboarding.tsx +++ b/src/screens/onboarding/ProfileOnboarding.tsx @@ -10,11 +10,23 @@ import { Alert, View, } from 'react-native'; +import { + Background, + TaggBigInput, + TaggInput, + TaggDatePicker, + TaggDropDown, +} from '../../components'; import {OnboardingStackParams} from '../../routes/onboarding'; import {AuthContext} from '../../routes/authentication'; -import {Background} from '../../components'; import ImagePicker from 'react-native-image-crop-picker'; -import {REGISTER_ENDPOINT} from '../../constants'; +import { + REGISTER_ENDPOINT, + websiteRegex, + bioRegex, + genderRegex, +} from '../../constants'; +import moment from 'moment'; type ProfileOnboardingScreenRouteProp = RouteProp< OnboardingStackParams, @@ -36,8 +48,51 @@ interface ProfileOnboardingProps { const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { const {userId, username} = route.params; - const [largePic, setLargePic] = React.useState(''); - const [smallPic, setSmallPic] = React.useState(''); + const [form, setForm] = React.useState({ + largePic: '', + smallPic: '', + website: '', + bio: '', + birthdate: '', + gender: '', + isValidWebsite: true, + isValidBio: true, + isValidGender: true, + attemptedSubmit: false, + }); + const [customGender, setCustomGender] = React.useState(); + + // refs for changing focus + const bioRef = React.useRef(); + const birthdateRef = React.useRef(); + const genderRef = React.useRef(); + const customGenderRef = React.useRef(); + /** + * Handles focus change to the next input field. + * @param field key for field to move focus onto + */ + const handleFocusChange = (field: string): void => { + switch (field) { + case 'bio': + const bioField: any = bioRef.current; + bioField.focus(); + break; + case 'birthdate': + const birthdateField: any = birthdateRef.current; + birthdateField.focus(); + break; + case 'gender': + const genderField: any = genderRef.current; + genderField.focus(); + break; + case 'customGender': + const customGenderField: any = customGenderRef.current; + customGenderField.focus(); + break; + default: + return; + } + }; /** * login: determines if user successully created an account to @@ -54,9 +109,9 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { accessibilityLabel="ADD LARGE PROFILE PIC HERE" onPress={goToGalleryLargePic} style={styles.largeProfile}> - {largePic ? ( + {form.largePic ? ( <Image - source={{uri: largePic}} + source={{uri: form.largePic}} style={[styles.largeProfile, styles.profilePic]} /> ) : ( @@ -74,9 +129,9 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { accessibilityLabel="ADD SMALLER PIC" onPress={goToGallerySmallPic} style={styles.smallProfile}> - {smallPic ? ( + {form.smallPic ? ( <Image - source={{uri: smallPic}} + source={{uri: form.smallPic}} style={[styles.smallProfile, styles.profilePic]} /> ) : ( @@ -99,7 +154,10 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { }) .then((picture) => { if ('path' in picture) { - setLargePic(picture.path); + setForm({ + ...form, + largePic: picture.path, + }); } }) .catch(() => {}); @@ -120,28 +178,140 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { }) .then((picture) => { if ('path' in picture) { - setSmallPic(picture.path); + setForm({ + ...form, + smallPic: picture.path, + }); } }) .catch(() => {}); }; + /* + * Handles changes to the website field value and verifies the input by updating state and running a validation function. + */ + const handleWebsiteUpdate = (website: string) => { + let isValidWebsite: boolean = websiteRegex.test(website); + setForm({ + ...form, + website, + isValidWebsite, + }); + }; + + /* + * Handles changes to the bio field value and verifies the input by updating state and running a validation function. + */ + const handleBioUpdate = (bio: string) => { + let isValidBio: boolean = bioRegex.test(bio); + setForm({ + ...form, + bio, + isValidBio, + }); + }; + + const handleGenderUpdate = (gender: string) => { + if (gender === 'custom') { + setCustomGender(true); + } else { + setCustomGender(false); + let isValidGender: boolean = true; + setForm({ + ...form, + gender, + isValidGender, + }); + } + }; + + const handleCustomGenderUpdate = (gender: string) => { + let isValidGender: boolean = genderRegex.test(gender); + gender = gender.replace(' ', '-'); + setForm({ + ...form, + gender, + isValidGender, + }); + }; + + const handleBirthdateUpdate = (birthdate: string) => { + setForm({ + ...form, + birthdate, + }); + }; + + const getMaxDate = () => { + const maxDate = moment().subtract(13, 'y').subtract(1, 'd'); + return maxDate.format('YYYY-MM-DD'); + }; + const handleSubmit = async () => { - const form = new FormData(); - if (largePic) { - form.append('largeProfilePicture', { - uri: largePic, + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + let invalidFields: boolean = false; + const request = new FormData(); + if (form.largePic) { + request.append('largeProfilePicture', { + uri: form.largePic, name: 'large_profile_pic.jpg', type: 'image/jpg', }); } - if (smallPic) { - form.append('smallProfilePicture', { - uri: smallPic, + if (form.smallPic) { + request.append('smallProfilePicture', { + uri: form.smallPic, name: 'small_profile_pic.jpg', type: 'image/jpg', }); } + if (form.website) { + if (form.isValidWebsite) { + request.append('website', form.website); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + invalidFields = true; + } + } + + if (form.bio) { + if (form.isValidBio) { + request.append('biography', form.bio); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + invalidFields = true; + } + } + + if (form.birthdate) { + request.append('birthday', form.birthdate); + } + + if (customGender) { + if (form.isValidGender) { + request.append('gender', form.gender); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + invalidFields = true; + } + } else { + if (form.isValidGender) { + request.append('gender', form.gender); + } + } + + if (invalidFields) { + return; + } + const endpoint = REGISTER_ENDPOINT + `${userId}/`; try { let response = await fetch(endpoint, { @@ -149,7 +319,7 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { headers: { 'Content-Type': 'multipart/form-data', }, - body: form, + body: request, }); let statusCode = response.status; let data = await response.json(); @@ -178,14 +348,82 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { return ( <Background centered> <StatusBar barStyle="light-content" /> - <LargeProfilePic /> - <SmallProfilePic /> - <View style={styles.dummyField}> - <Text>DUMMY WEBSITE</Text> - </View> - <View style={styles.dummyField}> - <Text>DUMMY BIO</Text> + <View style={styles.profile}> + <LargeProfilePic /> + <SmallProfilePic /> </View> + <TaggInput + accessibilityHint="Enter a website." + accessibilityLabel="Website input field." + placeholder="Website" + autoCompleteType="off" + textContentType="URL" + autoCapitalize="none" + returnKeyType="next" + onChangeText={handleWebsiteUpdate} + onSubmitEditing={() => handleFocusChange('bio')} + blurOnSubmit={false} + valid={form.isValidWebsite} + attemptedSubmit={form.attemptedSubmit} + invalidWarning={'Website must be a valid link to your website'} + width={280} + /> + <TaggBigInput + accessibilityHint="Enter a bio." + accessibilityLabel="Bio input field." + placeholder="Bio" + autoCompleteType="off" + textContentType="none" + autoCapitalize="none" + returnKeyType="next" + onChangeText={handleBioUpdate} + onSubmitEditing={() => handleFocusChange('bio')} + blurOnSubmit={false} + ref={bioRef} + valid={form.isValidBio} + attemptedSubmit={form.attemptedSubmit} + invalidWarning={ + 'Bio must be less than 150 characters and must contain valid characters' + } + width={280} + /> + <TaggDatePicker + date={form.birthdate} + maxDate={getMaxDate()} + onDateChange={(birthdate) => handleBirthdateUpdate(birthdate)} + /> + <TaggDropDown + onValueChange={(value) => handleGenderUpdate(value)} + items={[ + {label: 'Male', value: 'male'}, + {label: 'Female', value: 'female'}, + {label: 'Custom', value: 'custom'}, + ]} + placeholder={{ + label: 'Gender', + value: null, + color: '#ddd', + }} + /> + {customGender && ( + <TaggInput + accessibilityHint="Custom" + accessibilityLabel="Gender input field." + placeholder="Enter your gender" + autoCompleteType="off" + textContentType="none" + autoCapitalize="none" + returnKeyType="next" + blurOnSubmit={false} + ref={customGenderRef} + onChangeText={handleCustomGenderUpdate} + onSubmitEditing={() => handleSubmit()} + valid={form.isValidGender} + attemptedSubmit={form.attemptedSubmit} + invalidWarning={'Custom field can only contain letters and hyphens'} + width={280} + /> + )} <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn}> <Text style={styles.submitBtnLabel}>Let's start!</Text> </TouchableOpacity> @@ -194,6 +432,10 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { }; const styles = StyleSheet.create({ + profile: { + flexDirection: 'row', + marginBottom: '5%', + }, largeProfile: { justifyContent: 'center', alignItems: 'center', @@ -202,7 +444,8 @@ const styles = StyleSheet.create({ width: 230, borderRadius: 23, backgroundColor: '#fff', - marginRight: '6%', + marginLeft: '13%', + marginTop: '5%', }, largeProfileText: { textAlign: 'center', @@ -218,8 +461,8 @@ const styles = StyleSheet.create({ width: 110, borderRadius: 55, backgroundColor: '#E1F0FF', - marginLeft: '45%', - bottom: '7%', + right: '18%', + marginTop: '38%', }, smallProfileText: { textAlign: 'center', @@ -232,16 +475,6 @@ const styles = StyleSheet.create({ marginLeft: 0, bottom: 0, }, - dummyField: { - height: '10%', - width: '80%', - justifyContent: 'center', - alignItems: 'center', - borderColor: '#fff', - borderWidth: 1, - borderRadius: 8, - marginBottom: '10%', - }, submitBtn: { backgroundColor: '#8F01FF', justifyContent: 'center', @@ -249,6 +482,7 @@ const styles = StyleSheet.create({ width: 150, height: 40, borderRadius: 5, + marginTop: '5%', }, submitBtnLabel: { fontSize: 16, |