aboutsummaryrefslogtreecommitdiff
path: root/src/screens/onboarding/ProfileOnboarding.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/onboarding/ProfileOnboarding.tsx')
-rw-r--r--src/screens/onboarding/ProfileOnboarding.tsx310
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,