diff options
author | Shravya Ramesh <37447613+shravyaramesh@users.noreply.github.com> | 2020-11-17 18:06:14 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-17 21:06:14 -0500 |
commit | 713d169915a82edfcfe4b44622e3dce8c6adaf0c (patch) | |
tree | 3f0a0a9ef86e80442c4cc5b6b89be24cf1526268 /src | |
parent | 9b4ba92df514ca8c5c92c4f9279144e2c9d49e36 (diff) |
[TMA-382] Edit profile screen (#121)
* added more icon
* a less fat icon
* and the actual icon asset
* bottom drawer skeleton done
* removed warning, better code
* a more completed skeleton done
* bottom drawer done!
* Added content container, sent birthday picker props, minor styling
* differenciating defined and undefined birthdate in birthdate, datepicker
* removed restricting width for TaggDropDown
* Added edit profile screen to navigator stack
* Add EditProfile view, refresh profile view on save
* Removes unnecessary import
* Stores gender and birthdate as part of ProfileType
* Added gender, birthdate, isEditProfile to AuthProv
* Conditional view applied for edit profile button
* Includes discarded changes in previous merge- BD
* removed unused icon
* resolved scary warnings
* added icon to drawer
* Small fix
* minor code improvement
* sc
* fixed birthday bug
* custom gender updation fixed
* small change to birthday default value
* missed something
* cleaned up types! Warnings gone!
* fixed another gender picker bug
* fixed gender bug and cleaned up logic
* removed warning, MUCH better code now
Co-authored-by: Ivan Chen <ivan@thetaggid.com>
Co-authored-by: Ashm Walia <ashmwalia@outlook.com>
Diffstat (limited to 'src')
28 files changed, 1001 insertions, 115 deletions
diff --git a/src/assets/icons/more_horiz-24px.svg b/src/assets/icons/more_horiz-24px.svg new file mode 100644 index 00000000..3d4fc0b5 --- /dev/null +++ b/src/assets/icons/more_horiz-24px.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z"/><path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" fill="currentColor"/></svg>
\ No newline at end of file diff --git a/src/assets/ionicons/person-outline.svg b/src/assets/ionicons/person-outline.svg new file mode 100644 index 00000000..fa39dc76 --- /dev/null +++ b/src/assets/ionicons/person-outline.svg @@ -0,0 +1 @@ +<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Person</title><path d='M344 144c-3.92 52.87-44 96-88 96s-84.15-43.12-88-96c-4-55 35-96 88-96s92 42 88 96z' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32'/><path d='M256 304c-87 0-175.3 48-191.64 138.6C62.39 453.52 68.57 464 80 464h352c11.44 0 17.62-10.48 15.65-21.4C431.3 352 343 304 256 304z' fill='none' stroke='currentColor' stroke-miterlimit='10' stroke-width='32'/></svg>
\ No newline at end of file diff --git a/src/components/common/BottomDrawer.tsx b/src/components/common/BottomDrawer.tsx new file mode 100644 index 00000000..bef9434a --- /dev/null +++ b/src/components/common/BottomDrawer.tsx @@ -0,0 +1,118 @@ +import React, {Fragment, ReactText, useEffect, useRef, useState} from 'react'; +import { + Modal, + StyleSheet, + TouchableWithoutFeedback, + View, + ViewProps, +} from 'react-native'; +import Animated from 'react-native-reanimated'; +import BottomSheet from 'reanimated-bottom-sheet'; +import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +interface BottomDrawerProps extends ViewProps { + initialSnapPosition?: ReactText; + isOpen: boolean; + setIsOpen: (open: boolean) => void; + showHeader: boolean; +} + +// More examples here: +// https://github.com/osdnk/react-native-reanimated-bottom-sheet/tree/master/Example +const BottomDrawer: React.FC<BottomDrawerProps> = (props) => { + const {isOpen, setIsOpen, showHeader, initialSnapPosition} = props; + const drawerRef = useRef<BottomSheet>(null); + const [modalVisible, setModalVisible] = useState(isOpen); + const bgAlpha = new Animated.Value(isOpen ? 1 : 0); + + useEffect(() => { + if (isOpen) { + setModalVisible(true); + } else { + bgAlpha.setValue(0); + drawerRef.current && drawerRef.current.snapTo(1); + } + }, [isOpen]); + + const renderContent = () => { + return <View>{props.children}</View>; + }; + + const renderHeader = () => { + return showHeader ? ( + <View style={styles.header}> + <View style={styles.panelHeader}> + <View style={styles.panelHandle} /> + </View> + </View> + ) : ( + <Fragment /> + ); + }; + + return ( + <Modal + transparent + visible={modalVisible} + onShow={() => { + drawerRef.current && drawerRef.current.snapTo(0); + }}> + <BottomSheet + ref={drawerRef} + snapPoints={[initialSnapPosition ?? '30%', 0]} + initialSnap={1} + renderContent={renderContent} + renderHeader={renderHeader} + enabledContentGestureInteraction={false} + callbackNode={bgAlpha} + onCloseEnd={() => { + setModalVisible(false); + setIsOpen(false); + }} + /> + + <TouchableWithoutFeedback + onPress={() => { + setIsOpen(false); + }}> + <Animated.View + style={[ + styles.backgroundView, + { + backgroundColor: Animated.interpolateColors(bgAlpha, { + inputRange: [0, 1], + outputColorRange: ['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)'], + }), + }, + ]} + /> + </TouchableWithoutFeedback> + </Modal> + ); +}; + +const styles = StyleSheet.create({ + header: { + backgroundColor: '#f7f5eee8', + shadowColor: '#000000', + paddingTop: 20, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + }, + panelHeader: { + alignItems: 'center', + }, + panelHandle: { + width: 40, + height: 8, + borderRadius: 4, + backgroundColor: '#00000040', + marginBottom: 10, + }, + backgroundView: { + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + }, +}); + +export default BottomDrawer; diff --git a/src/components/common/SocialLinkModal.tsx b/src/components/common/SocialLinkModal.tsx index 3cea2567..b307a62c 100644 --- a/src/components/common/SocialLinkModal.tsx +++ b/src/components/common/SocialLinkModal.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {Modal, StyleSheet, Text, TouchableHighlight, View} from 'react-native'; import {TextInput} from 'react-native-gesture-handler'; +import { TAGG_TEXT_LIGHT_BLUE } from '../../constants'; import {SCREEN_WIDTH} from '../../utils'; interface SocialLinkModalProps { @@ -104,7 +105,7 @@ const styles = StyleSheet.create({ fontSize: 14, /* identical to box height */ textAlign: 'center', - color: '#698DD3', + color: TAGG_TEXT_LIGHT_BLUE, }, textInput: { height: 20, diff --git a/src/components/common/TaggDatePicker.tsx b/src/components/common/TaggDatePicker.tsx index d8010251..059bf620 100644 --- a/src/components/common/TaggDatePicker.tsx +++ b/src/components/common/TaggDatePicker.tsx @@ -1,3 +1,4 @@ +import moment from 'moment'; import React, {useState} from 'react'; import DatePicker from 'react-native-date-picker'; @@ -5,23 +6,24 @@ interface TaggDatePickerProps { handleDateUpdate: (_: Date) => void; maxDate: Date; textColor: string; + date: Date | undefined; } -const TaggDatePicker: React.FC<TaggDatePickerProps> = ({ - handleDateUpdate, - maxDate, - textColor, -}) => { - const [date, setDate] = useState(new Date()); +const TaggDatePicker: React.FC<TaggDatePickerProps> = (props) => { + const [date, setDate] = useState( + props.date + ? new Date(moment(props.date).add(1, 'day').format('YYYY-MM-DD')) + : undefined, + ); return ( <DatePicker - date={date} - textColor={textColor} + date={date ? date : props.maxDate} + textColor={props.textColor} mode={'date'} - maximumDate={maxDate} + maximumDate={props.maxDate} onDateChange={(newDate) => { setDate(newDate); - handleDateUpdate(newDate); + props.handleDateUpdate(newDate); }} /> ); diff --git a/src/components/common/index.ts b/src/components/common/index.ts index c7ed13cd..883dae61 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -13,4 +13,5 @@ export {default as SocialLinkModal} from './SocialLinkModal'; export {default as ComingSoon} from './ComingSoon'; export {default as PostCarousel} from './PostCarousel'; export {default as TaggDatePicker} from './TaggDatePicker'; +export {default as BottomDrawer} from './BottomDrawer'; export * from './post'; diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index f905bfe3..9e138ef3 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -6,7 +6,7 @@ import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler'; import LinearGradient from 'react-native-linear-gradient'; import PlusIcon from '../../assets/icons/plus_icon-01.svg'; import BigPlusIcon from '../../assets/icons/plus_icon-02.svg'; -import {MOMENTS_TITLE_COLOR} from '../../constants'; +import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {SCREEN_WIDTH} from '../../utils'; import ImagePicker from 'react-native-image-crop-picker'; import MomentTile from './MomentTile'; @@ -104,7 +104,7 @@ const styles = StyleSheet.create({ titleText: { fontSize: 16, fontWeight: 'bold', - color: MOMENTS_TITLE_COLOR, + color: TAGG_TEXT_LIGHT_BLUE, }, scrollContainer: { height: SCREEN_WIDTH / 2, diff --git a/src/components/onboarding/BirthDatePicker.tsx b/src/components/onboarding/BirthDatePicker.tsx index f97f1a72..0fc597c3 100644 --- a/src/components/onboarding/BirthDatePicker.tsx +++ b/src/components/onboarding/BirthDatePicker.tsx @@ -15,6 +15,8 @@ import {TaggDatePicker} from '../common'; interface BirthDatePickerProps extends TextInputProps { handleBDUpdate: (_: Date) => void; width?: number | string; + date: Date | undefined; + showPresetdate: boolean; } const BirthDatePicker = React.forwardRef( @@ -23,7 +25,7 @@ const BirthDatePicker = React.forwardRef( const maxDate = moment().subtract(13, 'y').subtract(1, 'd'); return maxDate.toDate(); }; - const [date, setDate] = useState(new Date(0)); + const [date, setDate] = useState(props.date); const [hidden, setHidden] = useState(true); const [updated, setUpdated] = useState(false); const textColor = updated ? 'white' : '#ddd'; @@ -42,7 +44,9 @@ const BirthDatePicker = React.forwardRef( style={[styles.input, {width: props.width}, {color: textColor}]} ref={ref} {...props}> - {updated ? moment(date).format('YYYY-MM-DD') : 'Date of Birth'} + {(updated || props.showPresetdate) && date + ? moment(date).format('YYYY-MM-DD') + : 'Date of Birth'} </Text> </TouchableOpacity> <Modal visible={!hidden} transparent={true} animationType="fade"> @@ -67,6 +71,7 @@ const BirthDatePicker = React.forwardRef( handleDateUpdate={updateDate} maxDate={getMaxDate()} textColor={'black'} + date={date} /> </View> </TouchableWithoutFeedback> diff --git a/src/components/onboarding/TaggDropDown.tsx b/src/components/onboarding/TaggDropDown.tsx index a45426ca..db531cc4 100644 --- a/src/components/onboarding/TaggDropDown.tsx +++ b/src/components/onboarding/TaggDropDown.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import RNSelectPicker from 'react-native-picker-select'; -import {View, StyleSheet, TextInputProps} from 'react-native'; +import {StyleSheet, View} from 'react-native'; +import RNSelectPicker, {PickerSelectProps} from 'react-native-picker-select'; -interface TaggDropDownProps extends TextInputProps { +interface TaggDropDownProps extends PickerSelectProps { width?: number | string; } @@ -19,7 +19,6 @@ const TaggDropDown = React.forwardRef((props: TaggDropDownProps, ref: any) => { const styles = StyleSheet.create({ container: { - width: '66.67%', alignItems: 'center', marginVertical: 11, }, diff --git a/src/components/profile/MoreInfoDrawer.tsx b/src/components/profile/MoreInfoDrawer.tsx new file mode 100644 index 00000000..719c1894 --- /dev/null +++ b/src/components/profile/MoreInfoDrawer.tsx @@ -0,0 +1,88 @@ +import {useNavigation} from '@react-navigation/native'; +import React, {useContext} from 'react'; +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; +import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; +import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {AuthContext} from '../../routes'; +import {BottomDrawer} from '../common'; +import PersonOutline from '../../assets/ionicons/person-outline.svg'; + +interface MoreInfoDrawerProps { + isOpen: boolean; + setIsOpen: (visible: boolean) => void; + isProfileView: boolean; +} + +const MoreInfoDrawer: React.FC<MoreInfoDrawerProps> = (props) => { + const insets = useSafeAreaInsets(); + const initialSnapPosition = 160 + insets.bottom; + const navigation = useNavigation(); + const { + user: {userId, username}, + } = useContext(AuthContext); + + const goToEditProfile = () => { + navigation.push('EditProfile', { + userId: userId, + username: username, + }); + props.setIsOpen(false); + }; + + return ( + <BottomDrawer + {...props} + showHeader={false} + initialSnapPosition={initialSnapPosition}> + <View style={styles.panel}> + <TouchableOpacity style={styles.panelButton} onPress={goToEditProfile}> + <PersonOutline style={styles.icon} /> + <Text style={styles.panelButtonTitle}>Edit Profile</Text> + </TouchableOpacity> + <View style={styles.divider} /> + <TouchableOpacity + style={styles.panelButton} + onPress={() => props.setIsOpen(false)}> + {/* Just a placeholder "icon" for easier alignment */} + <View style={styles.icon} /> + <Text style={styles.panelButtonTitleCancel}>Cancel</Text> + </TouchableOpacity> + </View> + </BottomDrawer> + ); +}; + +const styles = StyleSheet.create({ + panel: { + height: SCREEN_HEIGHT, + backgroundColor: 'white', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + }, + panelButton: { + height: 80, + flexDirection: 'row', + alignItems: 'center', + }, + panelButtonTitle: { + fontSize: 18, + fontWeight: 'bold', + color: 'black', + }, + icon: { + height: 25, + width: 25, + color: 'black', + marginLeft: SCREEN_WIDTH * 0.3, + marginRight: 25, + }, + panelButtonTitleCancel: { + fontSize: 18, + fontWeight: 'bold', + color: TAGG_TEXT_LIGHT_BLUE, + }, + divider: {height: 1, borderWidth: 1, borderColor: '#e7e7e7'}, +}); + +export default MoreInfoDrawer; diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index db8c6e0b..c0253533 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; -import {TOGGLE_BUTTON_TYPE} from '../../constants'; +import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants'; import {AuthContext, ProfileContext} from '../../routes/'; import ToggleButton from './ToggleButton'; @@ -80,7 +80,7 @@ const styles = StyleSheet.create({ }, website: { fontSize: 16, - color: '#4E699C', + color: TAGG_DARK_BLUE, marginBottom: 5, }, }); diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx index 6f11e806..62949746 100644 --- a/src/components/profile/ProfileHeader.tsx +++ b/src/components/profile/ProfileHeader.tsx @@ -1,10 +1,12 @@ -import React from 'react'; - +import React, {useState} from 'react'; +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; +import MoreIcon from '../../assets/icons/more_horiz-24px.svg'; +import {TAGG_DARK_BLUE} from '../../constants'; +import {AuthContext, ProfileContext} from '../../routes/'; +import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import Avatar from './Avatar'; +import MoreInfoDrawer from './MoreInfoDrawer'; import FollowCount from './FollowCount'; -import {View, Text, StyleSheet} from 'react-native'; -import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; -import {AuthContext, ProfileContext} from '../../routes/'; type ProfileHeaderProps = { isProfileView: boolean; @@ -22,8 +24,26 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ } = isProfileView ? React.useContext(ProfileContext) : React.useContext(AuthContext); + const [drawerVisible, setDrawerVisible] = useState(false); + return ( <View style={styles.container}> + {!isProfileView && ( + <> + <TouchableOpacity + style={styles.more} + onPress={() => { + setDrawerVisible(true); + }}> + <MoreIcon height={30} width={30} color={TAGG_DARK_BLUE} /> + </TouchableOpacity> + <MoreInfoDrawer + isOpen={drawerVisible} + setIsOpen={setDrawerVisible} + isProfileView={isProfileView} + /> + </> + )} <View style={styles.row}> <Avatar style={styles.avatar} isProfileView={isProfileView} /> <View style={styles.header}> @@ -51,8 +71,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ const styles = StyleSheet.create({ container: { top: SCREEN_HEIGHT / 2.4, - paddingHorizontal: SCREEN_WIDTH / 20, - marginBottom: SCREEN_HEIGHT / 10, + width: '100%', position: 'absolute', }, row: { @@ -76,6 +95,12 @@ const styles = StyleSheet.create({ follows: { marginHorizontal: SCREEN_HEIGHT / 50, }, + more: { + position: 'absolute', + right: '5%', + marginTop: '4%', + zIndex: 1, + }, }); export default ProfileHeader; diff --git a/src/components/profile/ToggleButton.tsx b/src/components/profile/ToggleButton.tsx index ff14cdde..4c8cb5b9 100644 --- a/src/components/profile/ToggleButton.tsx +++ b/src/components/profile/ToggleButton.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import {StyleSheet, Text} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; +import { TAGG_TEXT_LIGHT_BLUE } from '../../constants'; import {getToggleButtonText, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; type ToggleButtonProps = { @@ -31,14 +32,14 @@ const styles = StyleSheet.create({ height: SCREEN_WIDTH * 0.1, borderRadius: 8, marginTop: '3%', - borderColor: '#698dd3', + borderColor: TAGG_TEXT_LIGHT_BLUE, backgroundColor: 'white', borderWidth: 3, marginHorizontal: '1%', }, text: { fontWeight: 'bold', - color: '#698dd3', + color: TAGG_TEXT_LIGHT_BLUE, }, }); export default ToggleButton; diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts index 2e9c23ea..0f57347b 100644 --- a/src/components/profile/index.ts +++ b/src/components/profile/index.ts @@ -3,5 +3,6 @@ export {default as Content} from './Content'; export {default as ProfileCutout} from './ProfileCutout'; export {default as ProfileBody} from './ProfileBody'; export {default as ProfileHeader} from './ProfileHeader'; -export {default as ProfilePreview} from '../profile/ProfilePreview'; -export {default as Followers} from '../profile/Followers'; +export {default as ProfilePreview} from './ProfilePreview'; +export {default as Followers} from './Followers'; +export {default as MoreInfoDrawer} from './MoreInfoDrawer'; diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx index a5c08984..f47f8879 100644 --- a/src/components/search/RecentSearches.tsx +++ b/src/components/search/RecentSearches.tsx @@ -7,6 +7,7 @@ import { TouchableOpacityProps, } from 'react-native'; import {ProfilePreviewType} from 'src/types'; +import { TAGG_TEXT_LIGHT_BLUE } from '../../constants'; import SearchResults from './SearchResults'; interface RecentSearchesProps extends TouchableOpacityProps { @@ -45,7 +46,7 @@ const styles = StyleSheet.create({ clear: { fontSize: 17, fontWeight: 'bold', - color: '#698DD3', + color: TAGG_TEXT_LIGHT_BLUE, }, }); diff --git a/src/components/taggs/TwitterTaggPost.tsx b/src/components/taggs/TwitterTaggPost.tsx index 158b5995..fb4cbd0f 100644 --- a/src/components/taggs/TwitterTaggPost.tsx +++ b/src/components/taggs/TwitterTaggPost.tsx @@ -3,7 +3,7 @@ import {Image, Linking, StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import Hyperlink from 'react-native-hyperlink'; import LinearGradient from 'react-native-linear-gradient'; -import {AVATAR_DIM, TAGGS_GRADIENT} from '../../constants'; +import {AVATAR_DIM, TAGGS_GRADIENT, TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {TwitterPostType} from '../../types'; import {SCREEN_WIDTH} from '../../utils'; import {DateLabel, PostCarousel} from '../common'; @@ -238,7 +238,7 @@ const styles = StyleSheet.create({ }, replyShowThisThread: { fontSize: 15, - color: '#698dd3', + color: TAGG_TEXT_LIGHT_BLUE, }, }); diff --git a/src/constants/constants.ts b/src/constants/constants.ts index dbd79b45..c2003fb4 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -1,5 +1,7 @@ import {SCREEN_WIDTH, SCREEN_HEIGHT} from '../utils'; +export const CHIN_HEIGHT = 34; + export const PROFILE_CUTOUT_TOP_Y = SCREEN_HEIGHT / 2.3; export const PROFILE_CUTOUT_BOTTOM_Y = SCREEN_HEIGHT / 1.8; export const PROFILE_CUTOUT_CORNER_X = SCREEN_WIDTH / 2.9; @@ -46,6 +48,9 @@ export const LINKEDIN_FONT_COLOR: string = '#78B5FD'; export const SNAPCHAT_FONT_COLOR: string = '#FFFC00'; export const YOUTUBE_FONT_COLOR: string = '#FCA4A4'; +export const TAGG_DARK_BLUE = '#4E699C'; +export const TAGG_TEXT_LIGHT_BLUE: string = '#698DD3'; + export const TAGGS_GRADIENT = { start: '#9F00FF', end: '#27EAE9', @@ -81,5 +86,3 @@ export const defaultMoments: Array<string> = [ 'Creativity', 'Activity', ]; - -export const MOMENTS_TITLE_COLOR: string = '#698DD3'; diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx index 7da47b71..7046d04f 100644 --- a/src/routes/authentication/AuthProvider.tsx +++ b/src/routes/authentication/AuthProvider.tsx @@ -40,6 +40,8 @@ interface AuthContextProps { blockedUsers: ProfilePreviewType[]; blockedUsersNeedUpdate: boolean; updateBlockedUsers: (value: boolean) => void; + isEditedProfile: boolean; + updateIsEditedProfile: (value: boolean) => void; } const NO_USER: UserType = { @@ -51,6 +53,8 @@ const NO_PROFILE: ProfileType = { biography: '', website: '', name: '', + gender: '', + birthday: undefined, }; const NO_SOCIAL_ACCOUNTS: Record<string, SocialAccountType> = { @@ -79,6 +83,8 @@ export const AuthContext = createContext<AuthContextProps>({ blockedUsers: [], blockedUsersNeedUpdate: true, updateBlockedUsers: () => {}, + isEditedProfile: false, + updateIsEditedProfile: () => {}, }); /** @@ -110,6 +116,7 @@ const AuthProvider: React.FC = ({children}) => { const [blockedUsersNeedUpdate, setBlockedUsersNeedUpdate] = useState<boolean>( true, ); + const [isEditedProfile, setIsEditedProfile] = useState<boolean>(false); const {userId} = user; useEffect(() => { @@ -149,7 +156,7 @@ const AuthProvider: React.FC = ({children}) => { } }; loadData(); - }, [userId]); + }, [userId, isEditedProfile]); useEffect(() => { const loadNewMoments = async () => { @@ -245,6 +252,7 @@ const AuthProvider: React.FC = ({children}) => { followersNeedUpdate, blockedUsers, blockedUsersNeedUpdate, + isEditedProfile, login: (id, username) => { setUser({...user, userId: id, username}); }, @@ -274,6 +282,9 @@ const AuthProvider: React.FC = ({children}) => { updateBlockedUsers: (value) => { setBlockedUsersNeedUpdate(value); }, + updateIsEditedProfile: (value: boolean) => { + setIsEditedProfile(value); + }, }}> {children} </AuthContext.Provider> diff --git a/src/routes/profile/Profile.tsx b/src/routes/profile/Profile.tsx index bffa22ce..b6672c85 100644 --- a/src/routes/profile/Profile.tsx +++ b/src/routes/profile/Profile.tsx @@ -7,6 +7,7 @@ import { ProfileScreen, MomentCommentsScreen, FollowersListScreen, + EditProfile, } from '../../screens'; import {ProfileStack, ProfileStackParams} from './ProfileStack'; import {RouteProp} from '@react-navigation/native'; @@ -99,6 +100,17 @@ const Profile: React.FC<ProfileStackProps> = ({route}) => { component={FollowersListScreen} initialParams={{isProfileView: isProfileView}} /> + <ProfileStack.Screen + name="EditProfile" + component={EditProfile} + options={{ + headerShown: true, + headerTitle: 'Edit Profile', + headerTransparent: true, + headerBackTitleVisible: false, + headerTintColor: 'white', + }} + /> </ProfileStack.Navigator> ); }; diff --git a/src/routes/profile/ProfileStack.tsx b/src/routes/profile/ProfileStack.tsx index b1e86214..5590f78a 100644 --- a/src/routes/profile/ProfileStack.tsx +++ b/src/routes/profile/ProfileStack.tsx @@ -33,6 +33,10 @@ export type ProfileStackParams = { isFollowers: boolean; list: ProfilePreviewType[]; }; + EditProfile: { + userId: boolean; + username: ProfilePreviewType[]; + }; }; export const ProfileStack = createStackNavigator<ProfileStackParams>(); diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx index 3979de38..f21f3864 100644 --- a/src/screens/onboarding/ProfileOnboarding.tsx +++ b/src/screens/onboarding/ProfileOnboarding.tsx @@ -15,7 +15,6 @@ import { Background, TaggBigInput, TaggInput, - TaggDatePicker, TaggDropDown, BirthDatePicker, } from '../../components'; @@ -52,12 +51,13 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ navigation, }) => { const {userId, username} = route.params; + let emptyDate: string | undefined; const [form, setForm] = React.useState({ largePic: '', smallPic: '', website: '', bio: '', - birthdate: '', + birthdate: emptyDate, gender: '', isValidWebsite: true, isValidBio: true, @@ -65,7 +65,7 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ attemptedSubmit: false, token: '', }); - const [customGender, setCustomGender] = React.useState(); + const [customGender, setCustomGender] = React.useState(Boolean); // refs for changing focus const bioRef = React.useRef(); @@ -232,7 +232,7 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ const handleBirthdateUpdate = (birthdate: Date) => { setForm({ ...form, - birthdate: moment(birthdate).format('YYYY-MM-DD'), + birthdate: birthdate && moment(birthdate).format('YYYY-MM-DD'), }); }; @@ -357,81 +357,85 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ <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} - /> - <BirthDatePicker - ref={birthdateRef} - handleBDUpdate={handleBirthdateUpdate} - width={280} - /> - <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 && ( + <View style={styles.contentContainer}> <TaggInput - 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="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={customGenderRef} - onChangeText={handleCustomGenderUpdate} - onSubmitEditing={() => handleSubmit()} - valid={form.isValidGender} + ref={bioRef} + valid={form.isValidBio} attemptedSubmit={form.attemptedSubmit} - invalidWarning={'Custom field can only contain letters and hyphens'} + invalidWarning={ + 'Bio must be less than 150 characters and must contain valid characters' + } width={280} /> - )} - <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn}> - <Text style={styles.submitBtnLabel}>Let's start!</Text> - </TouchableOpacity> + <BirthDatePicker + ref={birthdateRef} + handleBDUpdate={handleBirthdateUpdate} + width={280} + date={form.birthdate} + showPresetdate={false} + /> + <TaggDropDown + onValueChange={(value: string) => 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> + </View> </Background> ); }; @@ -441,6 +445,11 @@ const styles = StyleSheet.create({ flexDirection: 'row', marginBottom: '5%', }, + contentContainer: { + position: 'relative', + width: 280, + alignSelf: 'center', + }, largeProfileUploader: { justifyContent: 'center', alignItems: 'center', @@ -493,6 +502,7 @@ const styles = StyleSheet.create({ height: 40, borderRadius: 5, marginTop: '5%', + alignSelf: 'center', }, submitBtnLabel: { fontSize: 16, diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx new file mode 100644 index 00000000..01b67155 --- /dev/null +++ b/src/screens/profile/EditProfile.tsx @@ -0,0 +1,591 @@ +import React, {useCallback, useEffect, useState} 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, + SafeAreaView, +} from 'react-native'; +import {Button} from 'react-native-elements'; +import { + Background, + TaggBigInput, + TaggInput, + TaggDropDown, + BirthDatePicker, + TabsGradient, +} 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'; +import {AuthContext} from '../../routes'; +import Animated from 'react-native-reanimated'; +import {SCREEN_HEIGHT} 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<ProfileOnboardingProps> = ({ + route, + navigation, +}) => { + const y: Animated.Value<number> = Animated.useValue(0); + const {userId, username} = route.params; + const { + profile: {website, biography, birthday, gender}, + avatar, + cover, + updateIsEditedProfile, + } = React.useContext(AuthContext); + const [needsUpdate, setNeedsUpdate] = useState(false); + + useEffect(() => { + updateIsEditedProfile(needsUpdate); + }, [needsUpdate, updateIsEditedProfile]); + + const [isCustomGender, setIsCustomGender] = React.useState<boolean>( + gender !== '' && gender !== 'female' && gender !== 'male', + ); + + const [form, setForm] = React.useState({ + largePic: cover ? cover : '', + smallPic: avatar ? avatar : '', + website: website ? website : '', + bio: biography ? biography : '', + birthdate: birthday && moment(birthday).format('YYYY-MM-DD'), + gender: isCustomGender ? 'custom' : gender, + customGenderText: isCustomGender ? gender : '', + isValidWebsite: true, + isValidBio: true, + isValidGender: 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 Large Profile Pic Here" button + */ + const LargeProfilePic = () => ( + <TouchableOpacity + accessible={true} + accessibilityLabel="ADD LARGE PROFILE PIC HERE" + onPress={goToGalleryLargePic} + style={styles.largeProfileUploader}> + {form.largePic ? ( + <Image source={{uri: form.largePic}} style={styles.largeProfilePic} /> + ) : ( + <Text style={styles.largeProfileText}>ADD LARGE PROFILE PIC HERE</Text> + )} + </TouchableOpacity> + ); + + /** + * Profile screen "Add Smaller Profile Pic Here" button + */ + const SmallProfilePic = () => ( + <TouchableOpacity + accessible={true} + accessibilityLabel="ADD SMALLER PIC" + onPress={goToGallerySmallPic} + style={styles.smallProfileUploader}> + {form.smallPic ? ( + <Image source={{uri: form.smallPic}} style={styles.smallProfilePic} /> + ) : ( + <Text style={styles.smallProfileText}>ADD SMALLER PIC</Text> + )} + </TouchableOpacity> + ); + + /* + * 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') { + setForm({...form, gender}); + setIsCustomGender(true); + } else if (gender === null) { + // not doing anything will make the picker "bounce back" + } else { + setIsCustomGender(false); + let isValidGender: boolean = true; + setForm({ + ...form, + gender, + isValidGender, + }); + } + }; + + const handleCustomGenderUpdate = (customGenderText: string) => { + let isValidGender: boolean = genderRegex.test(customGenderText); + customGenderText = customGenderText.replace(' ', '-'); + setForm({ + ...form, + customGenderText, + isValidGender, + }); + }; + + const handleBirthdateUpdate = (birthdate: Date) => { + setForm({ + ...form, + birthdate: birthdate && moment(birthdate).format('YYYY-MM-DD'), + }); + }; + + const handleSubmit = useCallback(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 (isCustomGender) { + if (form.isValidGender) { + request.append('gender', form.customGenderText); + } 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) { + setNeedsUpdate(true); + navigation.pop(); + } 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, + }; + } + }, [isCustomGender, form, navigation, userId]); + + React.useLayoutEffect(() => { + navigation.setOptions({ + headerRight: () => ( + <Button + title={'Save'} + buttonStyle={{backgroundColor: 'transparent'}} + titleStyle={{fontWeight: 'bold'}} + onPress={handleSubmit} + /> + ), + }); + }, [navigation, handleSubmit]); + + 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 && ( + <TaggInput + style={styles.customGenderInput} + value={form.customGenderText} + 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' + } + /> + )} + </View> + </View> + </View> + </Animated.ScrollView> + </SafeAreaView> + <TabsGradient /> + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + marginTop: '10%', + flex: 1, + flexDirection: 'column', + width: '100%', + }, + profile: { + flexDirection: 'row', + marginBottom: '5%', + justifyContent: 'flex-end', + }, + 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', + }, + customGenderInput: { + width: '100%', + height: 40, + fontSize: 16, + fontWeight: '600', + color: '#fff', + borderColor: '#fffdfd', + borderWidth: 2, + borderRadius: 20, + paddingLeft: 13, + }, +}); + +export default ProfileOnboarding; diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index 0d685ad1..7a0bfa66 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import {RouteProp, useNavigation} from '@react-navigation/native'; import {ProfileStackParams} from '../../routes/profile'; -import {CenteredView, CommentTile, OverlayView} from '../../components'; +import {CenteredView, CommentTile} from '../../components'; import {CommentType} from '../../types'; import {ScrollView, StyleSheet, Text, View} from 'react-native'; import {SCREEN_WIDTH} from '../../utils/screenDimensions'; diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx index 9579a696..d32eca98 100644 --- a/src/screens/profile/ProfileScreen.tsx +++ b/src/screens/profile/ProfileScreen.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React, {useContext, useEffect} from 'react'; import {StatusBar} from 'react-native'; import Animated from 'react-native-reanimated'; import {Content, Cover, TabsGradient} from '../../components'; import {RouteProp} from '@react-navigation/native'; -import {ProfileStackParams, ProfileProvider} from '../../routes/'; +import {ProfileStackParams, ProfileProvider, AuthContext} from '../../routes/'; /** * Profile Screen for a user's profile @@ -19,6 +19,11 @@ interface ProfileOnboardingProps { const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => { const {isProfileView, username, userId} = route.params; const y = Animated.useValue(0); + const {updateIsEditedProfile} = useContext(AuthContext); + + useEffect(() => { + updateIsEditedProfile(false); + }); const profileView = () => { return ( diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts index c2bd4c4b..3bfe5d30 100644 --- a/src/screens/profile/index.ts +++ b/src/screens/profile/index.ts @@ -4,3 +4,4 @@ export {default as CaptionScreen} from './CaptionScreen'; export {default as IndividualMoment} from './IndividualMoment'; export {default as MomentCommentsScreen} from './MomentCommentsScreen'; export {default as FollowersListScreen} from './FollowersListScreen'; +export {default as EditProfile} from './EditProfile'; diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx index f528358a..aef3d0a8 100644 --- a/src/screens/search/SearchScreen.tsx +++ b/src/screens/search/SearchScreen.tsx @@ -19,7 +19,7 @@ import { SearchResultsBackground, TabsGradient, } from '../../components'; -import {SEARCH_ENDPOINT} from '../../constants'; +import {SEARCH_ENDPOINT, TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {AuthContext} from '../../routes/authentication'; import {ProfilePreviewType, UserType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; @@ -191,7 +191,7 @@ const styles = StyleSheet.create({ clear: { fontSize: 17, fontWeight: 'bold', - color: '#698DD3', + color: TAGG_TEXT_LIGHT_BLUE, }, image: { width: SCREEN_WIDTH, diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index 6e1d1ef5..38e04221 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -1,6 +1,7 @@ //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'; @@ -29,8 +30,10 @@ export const loadProfileInfo = async ( const status = response.status; if (status === 200) { const info = await response.json(); - let {name, biography, website} = info; - callback({name, biography, website}); + let {name, biography, website, birthday, gender} = info; + // user should always have a birthday, but a safety check here + birthday = birthday && moment(birthday).format('YYYY-MM-DD'); + callback({name, biography, website, birthday, gender}); } } catch (error) { Alert.alert( diff --git a/src/types/types.ts b/src/types/types.ts index 8b55e113..8336919e 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -17,6 +17,8 @@ export interface ProfileType { name: string; biography: string; website: string; + gender: string; + birthday: Date | undefined; } export interface SocialAccountType { |