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, TaggBigInput, TaggDropDown, TaggInput, } from '../../../components'; import { bioRegex, CLASS_YEAR_LIST, EDIT_PROFILE_ENDPOINT, genderRegex, TAGG_PURPLE, websiteRegex, } from '../../../constants'; import { ERROR_DOUBLE_CHECK_CONNECTION, ERROR_PROFILE_CREATION_SHORT, ERROR_SELECT_CLASS_YEAR, ERROR_SOMETHING_WENT_WRONG_REFRESH, ERROR_UPLOAD_LARGE_PROFILE_PIC, ERROR_UPLOAD_SMALL_PROFILE_PIC, } from '../../../constants/strings'; import {OnboardingStackParams} from '../../../routes/onboarding'; import {BackgroundGradientType} from '../../../types'; import {SCREEN_WIDTH} from '../../../utils'; 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; let emptyDate: string | undefined; const [form, setForm] = React.useState({ largePic: '', smallPic: '', website: '', bio: '', birthdate: emptyDate, gender: '', isValidWebsite: true, isValidBio: true, isValidGender: true, attemptedSubmit: false, token: '', classYear: -1, }); const [customGender, setCustomGender] = React.useState(Boolean); // 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; } }; var classYearList: Array = []; CLASS_YEAR_LIST.map((value) => { classYearList.push({label: value, value: value}); }); /** * Profile screen "Add header image" button */ const LargeProfilePic = () => ( {form.largePic ? ( ) : ( ADD HEADER IMAGE )} ); /** * Profile screen "Add profile picture" button */ const SmallProfilePic = () => ( {form.smallPic ? ( ) : ( ADD PROFILE PICTURE )} ); const goToGalleryLargePic = () => { ImagePicker.openPicker({ smartAlbums: [ 'Favorites', 'RecentlyAdded', 'SelfPortraits', 'Screenshots', 'UserLibrary', ], width: 580, height: 580, cropping: true, cropperToolbarTitle: 'Select Header', mediaType: 'photo', }).then((picture) => { if ('path' in picture) { setForm({ ...form, largePic: picture.path, }); } }); }; 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, }); } }); }; /* * 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 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.largePic) { Alert.alert(ERROR_UPLOAD_LARGE_PROFILE_PIC); return; } if (!form.smallPic) { Alert.alert(ERROR_UPLOAD_SMALL_PROFILE_PIC); return; } if (form.classYear === -1) { Alert.alert(ERROR_SELECT_CLASS_YEAR); 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 (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, }); 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(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 ( ); }, [form.largePic, form.smallPic]); return ( {profilePics} 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} /> 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: 'space-around', marginBottom: '10%', }, profile: { flexDirection: 'row', marginBottom: '5%', }, contentContainer: { position: 'relative', width: 280, alignSelf: 'center', }, largeProfileUploader: { justifyContent: 'center', alignItems: 'center', padding: 15, backgroundColor: '#fff', marginLeft: '13%', marginTop: '10%', 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: TAGG_PURPLE, justifyContent: 'center', alignItems: 'center', width: SCREEN_WIDTH / 2.5, height: SCREEN_WIDTH / 10, borderRadius: 5, marginTop: '5%', alignSelf: 'center', marginBottom: SCREEN_WIDTH / 3, }, submitBtnLabel: { fontSize: 16, fontWeight: '500', color: '#fff', }, }); export default ProfileOnboarding;