import React from 'react'; import {RouteProp} from '@react-navigation/native'; import moment from 'moment'; import {StackNavigationProp} from '@react-navigation/stack'; import { Text, StatusBar, StyleSheet, Image, TouchableOpacity, Alert, View, } from 'react-native'; import { Background, TaggBigInput, TaggInput, TaggDatePicker, TaggDropDown, BirthDatePicker, } from '../../components'; import {OnboardingStackParams} from '../../routes/onboarding'; import ImagePicker from 'react-native-image-crop-picker'; import { EDIT_PROFILE_ENDPOINT, websiteRegex, bioRegex, genderRegex, } from '../../constants'; import AsyncStorage from '@react-native-community/async-storage'; type ProfileOnboardingScreenRouteProp = RouteProp< OnboardingStackParams, 'ProfileOnboarding' >; type ProfileOnboardingScreenNavigationProp = StackNavigationProp< OnboardingStackParams, 'ProfileOnboarding' >; interface ProfileOnboardingProps { route: ProfileOnboardingScreenRouteProp; navigation: ProfileOnboardingScreenNavigationProp; } /** * Create profile screen for onboarding. * @param navigation react-navigation navigation object */ const ProfileOnboarding: React.FC = ({ route, navigation, }) => { const {userId, username} = route.params; const [form, setForm] = React.useState({ largePic: '', smallPic: '', website: '', bio: '', birthdate: '', gender: '', isValidWebsite: true, isValidBio: true, isValidGender: true, attemptedSubmit: false, token: '', }); 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; } }; /** * Profile screen "Add Large Profile Pic Here" button */ const LargeProfilePic = () => ( {form.largePic ? ( ) : ( ADD LARGE PROFILE PIC HERE )} ); /** * Profile screen "Add Smaller Profile Pic Here" button */ const SmallProfilePic = () => ( {form.smallPic ? ( ) : ( ADD SMALLER PIC )} ); /* * Handles tap on add profile picture buttons by navigating to camera access * and selecting a picture from gallery for large profile picture */ const goToGalleryLargePic = () => { ImagePicker.openPicker({ width: 580, height: 580, cropping: true, cropperToolbarTitle: 'Large profile picture', mediaType: 'photo', }) .then((picture) => { if ('path' in picture) { setForm({ ...form, largePic: picture.path, }); } }) .catch(() => {}); }; /* * Handles tap on add profile picture buttons by navigating to camera access * and selecting a picture from gallery for small profile picture */ const goToGallerySmallPic = () => { ImagePicker.openPicker({ width: 580, height: 580, cropping: true, cropperToolbarTitle: 'Small profile picture', mediaType: 'photo', cropperCircleOverlay: true, }) .then((picture) => { if ('path' in picture) { 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) => { website = website.trim(); let isValidWebsite: boolean = websiteRegex.test(website); setForm({ ...form, website, isValidWebsite, }); }; /* * Handles changes to the bio field value and verifies the input by updating state and running a validation function. */ const handleBioUpdate = (bio: string) => { let isValidBio: boolean = bioRegex.test(bio); setForm({ ...form, bio, isValidBio, }); }; const handleGenderUpdate = (gender: string) => { if (gender === 'custom') { setCustomGender(true); } else { setCustomGender(false); let isValidGender: boolean = true; setForm({ ...form, gender, isValidGender, }); } }; const handleCustomGenderUpdate = (gender: string) => { let isValidGender: boolean = genderRegex.test(gender); gender = gender.replace(' ', '-'); setForm({ ...form, gender, isValidGender, }); }; const handleBirthdateUpdate = (birthdate: Date) => { setForm({ ...form, birthdate: moment(birthdate).format('YYYY-MM-DD'), }); }; const handleSubmit = async () => { if (!form.largePic) { Alert.alert('Please upload a large profile picture!'); return; } if (!form.smallPic) { Alert.alert('Please upload a small profile picture!'); return; } if (!form.attemptedSubmit) { setForm({ ...form, attemptedSubmit: true, }); } let invalidFields: boolean = false; const request = new FormData(); if (form.largePic) { request.append('largeProfilePicture', { uri: form.largePic, name: 'large_profile_pic.jpg', type: 'image/jpg', }); } if (form.smallPic) { request.append('smallProfilePicture', { uri: form.smallPic, name: 'small_profile_pic.jpg', type: 'image/jpg', }); } if (form.website) { if (form.isValidWebsite) { request.append('website', form.website); } else { setForm({...form, attemptedSubmit: false}); setTimeout(() => setForm({...form, attemptedSubmit: true})); invalidFields = true; } } if (form.bio) { if (form.isValidBio) { request.append('biography', form.bio); } else { setForm({...form, attemptedSubmit: false}); setTimeout(() => setForm({...form, attemptedSubmit: true})); invalidFields = true; } } if (form.birthdate) { request.append('birthday', form.birthdate); } if (customGender) { if (form.isValidGender) { request.append('gender', form.gender); } else { setForm({...form, attemptedSubmit: false}); setTimeout(() => setForm({...form, attemptedSubmit: true})); invalidFields = true; } } else { if (form.isValidGender) { request.append('gender', form.gender); } } if (invalidFields) { return; } const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`; try { const token = await AsyncStorage.getItem('token'); let response = await fetch(endpoint, { method: 'PATCH', headers: { 'Content-Type': 'multipart/form-data', Authorization: 'Token ' + token, }, body: request, }); let statusCode = response.status; let data = await response.json(); if (statusCode === 200) { navigation.navigate('SocialMedia', { userId: userId, username: username, }); } else if (statusCode === 400) { Alert.alert( 'Profile update failed. 😔', data.error || 'Something went wrong! 😭', ); } else { Alert.alert( 'Something went wrong! 😭', "Would you believe me if I told you that I don't know what happened?", ); } } catch (error) { Alert.alert( 'Profile creation failed 😓', 'Please double-check your network connection and retry.', ); return { name: 'Profile creation error', description: error, }; } }; return ( handleFocusChange('bio')} blurOnSubmit={false} valid={form.isValidWebsite} attemptedSubmit={form.attemptedSubmit} invalidWarning={'Website must be a valid link to your website'} width={280} /> 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} /> handleGenderUpdate(value)} items={[ {label: 'Male', value: 'male'}, {label: 'Female', value: 'female'}, {label: 'Custom', value: 'custom'}, ]} placeholder={{ label: 'Gender', value: null, color: '#ddd', }} /> {customGender && ( handleSubmit()} valid={form.isValidGender} attemptedSubmit={form.attemptedSubmit} invalidWarning={'Custom field can only contain letters and hyphens'} width={280} /> )} Let's start! ); }; const styles = StyleSheet.create({ profile: { flexDirection: 'row', marginBottom: '5%', }, largeProfileUploader: { justifyContent: 'center', alignItems: 'center', padding: 15, backgroundColor: '#fff', marginLeft: '13%', marginTop: '5%', height: 230, width: 230, borderRadius: 23, }, largeProfileText: { textAlign: 'center', fontSize: 14, fontWeight: 'bold', color: '#863FF9', }, largeProfilePic: { height: 230, width: 230, borderRadius: 23, }, smallProfileUploader: { justifyContent: 'center', alignItems: 'center', padding: 20, backgroundColor: '#E1F0FF', right: '18%', marginTop: '38%', height: 110, width: 110, borderRadius: 55, }, smallProfileText: { textAlign: 'center', fontSize: 14, fontWeight: 'bold', color: '#806DF4', }, smallProfilePic: { height: 110, width: 110, borderRadius: 55, }, submitBtn: { backgroundColor: '#8F01FF', justifyContent: 'center', alignItems: 'center', width: 150, height: 40, borderRadius: 5, marginTop: '5%', }, submitBtnLabel: { fontSize: 16, fontWeight: '500', color: '#fff', }, }); export default ProfileOnboarding;