diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/taggs/Tagg.tsx | 5 | ||||
-rw-r--r-- | src/components/taggs/TaggsBar.tsx | 11 | ||||
-rw-r--r-- | src/screens/profile/EditProfile.tsx | 348 | ||||
-rw-r--r-- | src/services/UserProfileService.ts | 9 | ||||
-rw-r--r-- | src/store/actions/user.ts | 24 | ||||
-rw-r--r-- | src/store/initialStates.ts | 2 | ||||
-rw-r--r-- | src/store/reducers/userReducer.ts | 18 | ||||
-rw-r--r-- | src/types/types.ts | 2 |
8 files changed, 253 insertions, 166 deletions
diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx index 086b3c87..8158cbac 100644 --- a/src/components/taggs/Tagg.tsx +++ b/src/components/taggs/Tagg.tsx @@ -26,7 +26,7 @@ interface TaggProps { isLinked: boolean; isIntegrated: boolean; setTaggsNeedUpdate: (_: boolean) => void; - setSocialDataNeedUpdate: (_: string) => void; + setSocialDataNeedUpdate: (social: string, username: string) => void; userXId: string; screenType: ScreenType; } @@ -84,7 +84,7 @@ const Tagg: React.FC<TaggProps> = ({ if (isIntegrated) { handlePressForAuthBrowser(social).then((success) => { setTaggsNeedUpdate(success); - if (success) setSocialDataNeedUpdate(social); + if (success) setSocialDataNeedUpdate(social, ''); }); } else { setModalVisible(true); @@ -112,6 +112,7 @@ const Tagg: React.FC<TaggProps> = ({ if (await registerNonIntegratedSocialLink(social, username)) { Alert.alert(`Successfully linked ${social} 🎉`); setTaggsNeedUpdate(true); + setSocialDataNeedUpdate(social, username); } else { // If we display too fast the alert will get dismissed with the modal setTimeout(() => { diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index 12e4b93a..082743d0 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -12,7 +12,7 @@ import {StatusBarHeight} from '../../utils'; import Tagg from './Tagg'; import {RootState} from '../../store/rootReducer'; import {ScreenType} from '../../types'; -import {loadIndividualSocial} from '../../store/actions'; +import {loadIndividualSocial, updateSocial} from '../../store/actions'; const {View, ScrollView, interpolate, Extrapolate} = Animated; interface TaggsBarProps { @@ -38,10 +38,15 @@ const TaggsBar: React.FC<TaggsBarProps> = ({ /** * Updates the individual social that needs update + * If username is empty, update nonintegrated socials like Snapchat and TikTok * @param socialType Type of the social that needs update */ - const handleSocialUpdate = (socialType: string) => { - dispatch(loadIndividualSocial(user.userId, socialType)); + const handleSocialUpdate = (socialType: string, username: string) => { + if (username !== '') { + dispatch(updateSocial(socialType, username)); + } else { + dispatch(loadIndividualSocial(user.userId, socialType)); + } }; /** diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx index ab58db41..97c58177 100644 --- a/src/screens/profile/EditProfile.tsx +++ b/src/screens/profile/EditProfile.tsx @@ -11,6 +11,9 @@ import { Alert, View, SafeAreaView, + KeyboardAvoidingView, + Platform, + Keyboard, } from 'react-native'; import {Button} from 'react-native-elements'; import { @@ -21,7 +24,6 @@ import { BirthDatePicker, TabsGradient, } from '../../components'; -import {OnboardingStackParams} from '../../routes/onboarding'; import ImagePicker from 'react-native-image-crop-picker'; import { EDIT_PROFILE_ENDPOINT, @@ -30,23 +32,21 @@ import { genderRegex, } from '../../constants'; import AsyncStorage from '@react-native-community/async-storage'; +import {ProfileStackParams} from '../../routes'; import Animated from 'react-native-reanimated'; import {HeaderHeight, SCREEN_HEIGHT} from '../../utils'; import {RootState} from '../../store/rootReducer'; import {useDispatch, useSelector} from 'react-redux'; import {loadUserData} from '../../store/actions'; -type ProfileOnboardingScreenRouteProp = RouteProp< - OnboardingStackParams, - 'ProfileOnboarding' +type EditProfileNavigationProp = StackNavigationProp< + ProfileStackParams, + 'EditProfile' >; -type ProfileOnboardingScreenNavigationProp = StackNavigationProp< - OnboardingStackParams, - 'ProfileOnboarding' ->; -interface ProfileOnboardingProps { - route: ProfileOnboardingScreenRouteProp; - navigation: ProfileOnboardingScreenNavigationProp; + +interface EditProfileProps { + route: RouteProp<ProfileStackParams, 'EditProfile'>; + navigation: EditProfileNavigationProp; } /** @@ -54,14 +54,11 @@ interface ProfileOnboardingProps { * @param navigation react-navigation navigation object */ -const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ - route, - navigation, -}) => { +const EditProfile: React.FC<EditProfileProps> = ({route, navigation}) => { const y: Animated.Value<number> = Animated.useValue(0); const {userId, username} = route.params; const { - profile: {website, biography, birthday, gender}, + profile: {website, biography, birthday, gender, snapchat, tiktok}, avatar, cover, } = useSelector((state: RootState) => state.user); @@ -73,7 +70,8 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ if (needsUpdate) { dispatch(loadUserData({userId, username})); } - }, [loadUserData, needsUpdate]); + + }, [dispatch, needsUpdate, userId, username]); const [isCustomGender, setIsCustomGender] = React.useState<boolean>( gender !== '' && gender !== 'female' && gender !== 'male', @@ -87,42 +85,15 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ birthdate: birthday && moment(birthday).format('YYYY-MM-DD'), gender: isCustomGender ? 'custom' : gender, customGenderText: isCustomGender ? gender : '', + snapchat: snapchat, + tiktok: tiktok, isValidWebsite: true, isValidBio: true, isValidGender: true, + isValidSnapchat: true, + isValidTiktok: true, attemptedSubmit: false, }); - // 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 header image" button @@ -256,6 +227,24 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ }); }; + const handleSnapchatUpdate = (newUsername: string) => { + // Allow any username, empty means to "un-link" it + // TODO: refresh taggs bar after + setForm({ + ...form, + snapchat: newUsername, + }); + }; + + const handleTikTokUpdate = (newUsername: string) => { + // Allow any username, empty means to "un-link" it + // TODO: refresh taggs bar after + setForm({ + ...form, + tiktok: newUsername, + }); + }; + const handleSubmit = useCallback(async () => { if (!form.largePic) { Alert.alert('Please select a Header image!'); @@ -325,6 +314,22 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ } } + if (form.isValidSnapchat) { + request.append('snapchat', form.snapchat); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + invalidFields = true; + } + + if (form.isValidTiktok) { + request.append('tiktok', form.tiktok); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + invalidFields = true; + } + if (invalidFields) { return; } @@ -357,10 +362,7 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ ); } } catch (error) { - Alert.alert( - 'Profile creation failed 😓', - 'Please double-check your network connection and retry.', - ); + Alert.alert('Please double-check your network connection and retry.'); return { name: 'Profile creation error', description: error, @@ -384,118 +386,158 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ return ( <Background centered> <SafeAreaView> - <Animated.ScrollView - style={styles.container} - onScroll={(e) => y.setValue(e.nativeEvent.contentOffset.y)} - showsHorizontalScrollIndicator={false} - showsVerticalScrollIndicator={true} - scrollEventThrottle={1} - alwaysBounceVertical - contentContainerStyle={{paddingBottom: SCREEN_HEIGHT / 15}}> - <StatusBar barStyle="light-content" translucent={false} /> - <View - style={{ - position: 'relative', - alignSelf: 'center', - }}> - <View> - <View style={styles.profile}> - <LargeProfilePic /> - <SmallProfilePic /> - </View> - <View - style={{ - position: 'relative', - width: 280, - alignSelf: 'center', - }}> - <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} - value={form.website} - /> - <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} - value={form.bio} - /> - <BirthDatePicker - ref={birthdateRef} - handleBDUpdate={handleBirthdateUpdate} - width={280} - date={form.birthdate} - showPresetdate={true} - /> - - <TaggDropDown - value={form.gender} - onValueChange={(value: string) => handleGenderUpdate(value)} - items={[ - {label: 'Male', value: 'male'}, - {label: 'Female', value: 'female'}, - {label: 'Custom', value: 'custom'}, - ]} - placeholder={{ - label: 'Gender', - value: null, - color: '#fff', - }} - /> - {isCustomGender && ( + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'}> + <Animated.ScrollView + style={styles.container} + onScroll={(e) => y.setValue(e.nativeEvent.contentOffset.y)} + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + scrollEventThrottle={1} + alwaysBounceVertical + contentContainerStyle={{paddingBottom: SCREEN_HEIGHT / 15}}> + <StatusBar barStyle="light-content" translucent={false} /> + <View + style={{ + position: 'relative', + alignSelf: 'center', + }}> + <View> + <View style={styles.profile}> + <LargeProfilePic /> + <SmallProfilePic /> + </View> + <View + style={{ + position: 'relative', + width: 280, + alignSelf: 'center', + }}> <TaggInput - style={styles.customGenderInput} - value={form.customGenderText} - accessibilityHint="Custom" - accessibilityLabel="Gender input field." - placeholder="Enter your gender" + accessibilityHint="Enter a website." + accessibilityLabel="Website input field." + placeholder="Website" + autoCompleteType="off" + textContentType="URL" + autoCapitalize="none" + returnKeyType="done" + onChangeText={handleWebsiteUpdate} + blurOnSubmit={false} + valid={form.isValidWebsite} + attemptedSubmit={form.attemptedSubmit} + invalidWarning={ + 'Website must be a valid link to your website' + } + width={280} + value={form.website} + /> + <TaggBigInput + accessibilityHint="Enter a bio." + accessibilityLabel="Bio input field." + placeholder="Bio" autoCompleteType="off" textContentType="none" autoCapitalize="none" returnKeyType="next" + onChangeText={handleBioUpdate} blurOnSubmit={false} - ref={customGenderRef} - onChangeText={handleCustomGenderUpdate} - onSubmitEditing={() => handleSubmit()} - valid={form.isValidGender} + valid={form.isValidBio} attemptedSubmit={form.attemptedSubmit} invalidWarning={ - 'Custom field can only contain letters and hyphens' + 'Bio must be less than 150 characters and must contain valid characters' } + width={280} + value={form.bio} + /> + <BirthDatePicker + handleBDUpdate={handleBirthdateUpdate} + width={280} + date={moment(form.birthdate).toDate()} + showPresetdate={true} + /> + + <TaggDropDown + value={form.gender} + onValueChange={(value: string) => handleGenderUpdate(value)} + items={[ + {label: 'Male', value: 'male'}, + {label: 'Female', value: 'female'}, + {label: 'Custom', value: 'custom'}, + ]} + placeholder={{ + label: 'Gender', + value: null, + color: '#fff', + }} /> - )} + {isCustomGender && ( + <TaggInput + accessibilityHint="Custom" + accessibilityLabel="Gender input field." + attemptedSubmit={form.attemptedSubmit} + autoCapitalize="none" + autoCompleteType="off" + blurOnSubmit={false} + invalidWarning={ + 'Custom field can only contain letters and hyphens' + } + onChangeText={handleCustomGenderUpdate} + onSubmitEditing={() => handleSubmit()} + placeholder="Enter your gender" + returnKeyType="done" + style={styles.customGenderInput} + textContentType="none" + valid={form.isValidGender} + value={form.customGenderText} + /> + )} + {snapchat !== '' && ( + <TaggInput + accessibilityHint="Snapchat Username" + accessibilityLabel="Snapchat Username Input Field." + attemptedSubmit={form.attemptedSubmit} + autoCapitalize="none" + autoCompleteType="off" + autoCorrect={false} + blurOnSubmit={false} + enablesReturnKeyAutomatically={true} + invalidWarning={'Please enter something!'} + onChangeText={handleSnapchatUpdate} + onSubmitEditing={Keyboard.dismiss} + placeholder="Snapchat Username" + returnKeyType="done" + textContentType="none" + valid={form.isValidSnapchat} + value={form.snapchat && form.snapchat} + width={280} + /> + )} + {tiktok !== '' && ( + <TaggInput + accessibilityHint="TikTok Username" + accessibilityLabel="TikTok Username Input Field." + attemptedSubmit={form.attemptedSubmit} + autoCapitalize="none" + autoCompleteType="off" + autoCorrect={false} + blurOnSubmit={false} + enablesReturnKeyAutomatically={true} + invalidWarning={'Please enter something!'} + onChangeText={handleTikTokUpdate} + onSubmitEditing={Keyboard.dismiss} + placeholder="TikTok Username" + returnKeyType="done" + textContentType="none" + valid={form.isValidTiktok} + value={form.tiktok} + width={280} + /> + )} + </View> </View> </View> - </View> - </Animated.ScrollView> + </Animated.ScrollView> + </KeyboardAvoidingView> </SafeAreaView> <TabsGradient /> </Background> @@ -585,4 +627,4 @@ const styles = StyleSheet.create({ }, }); -export default ProfileOnboarding; +export default EditProfile; diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index e69e4103..c8dbcdd1 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -1,10 +1,8 @@ -//Abstracted common profile api calls out here - import AsyncStorage from '@react-native-community/async-storage'; import moment from 'moment'; import {Alert} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; -import {SocialAccountType, MomentType} from 'src/types'; +import {SocialAccountType} from 'src/types'; import { AVATAR_PHOTO_ENDPOINT, COVER_PHOTO_ENDPOINT, @@ -25,10 +23,9 @@ export const loadProfileInfo = async (token: string, userId: string) => { const status = response.status; if (status === 200) { const info = await response.json(); - let {name, biography, website, birthday, gender} = info; - // user should always have a birthday, but a safety check here + let {name, biography, website, birthday, gender, snapchat, tiktok} = info; birthday = birthday && moment(birthday).format('YYYY-MM-DD'); - return {name, biography, website, birthday, gender}; + return {name, biography, website, birthday, gender, snapchat, tiktok}; } } catch (error) { Alert.alert( diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index 09ec8abf..e77b8513 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -2,7 +2,7 @@ import {RootState} from '../rootReducer'; import {UserType} from '../../types/types'; import {loadProfileInfo, loadAvatar, loadCover} from '../../services'; import {Action, ThunkAction} from '@reduxjs/toolkit'; -import {userLoggedIn, userDetailsFetched} from '../reducers'; +import {userLoggedIn, userDetailsFetched, socialEdited} from '../reducers'; import {getTokenOrLogout} from '../../utils'; /** @@ -38,6 +38,28 @@ export const loadUserData = ( } }; +/** + * To update editable socials + * @param social social to be updated + * @param value username of social to be updated + */ +export const updateSocial = ( + social: string, + value: string, +): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async ( + dispatch, +) => { + try { + console.log(social); + dispatch({ + type: socialEdited.type, + payload: {social, value}, + }); + } catch (error) { + console.log(error); + } +}; + export const logout = (): ThunkAction< Promise<void>, RootState, diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index 4087b97c..817af86b 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -14,6 +14,8 @@ export const NO_PROFILE: ProfileType = { name: '', gender: '', birthday: undefined, + snapchat: '', + tiktok: '', }; export const EMPTY_MOMENTS_LIST = <MomentType[]>[]; diff --git a/src/store/reducers/userReducer.ts b/src/store/reducers/userReducer.ts index f43bd0bc..2fd5c462 100644 --- a/src/store/reducers/userReducer.ts +++ b/src/store/reducers/userReducer.ts @@ -29,8 +29,24 @@ const userDataSlice = createSlice({ state.avatar = action.payload.avatar; state.cover = action.payload.cover; }, + + socialEdited: (state, action) => { + const {social, value} = action.payload; + switch (social) { + case 'Snapchat': + state.profile.snapchat = value; + break; + case 'TikTok': + state.profile.tiktok = value; + break; + } + }, }, }); -export const {userLoggedIn, userDetailsFetched} = userDataSlice.actions; +export const { + userLoggedIn, + userDetailsFetched, + socialEdited, +} = userDataSlice.actions; export const userDataReducer = userDataSlice.reducer; diff --git a/src/types/types.ts b/src/types/types.ts index 51837a91..79f15ae9 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -19,6 +19,8 @@ export interface ProfileType { website: string; gender: string; birthday: Date | undefined; + snapchat: string; + tiktok: string; } export interface SocialAccountType { |