From 0fd892ad288f2e1eaaa4fdf5e1fd6f15dbd45860 Mon Sep 17 00:00:00 2001 From: Ashm Walia <40498934+ashmgarv@users.noreply.github.com> Date: Fri, 4 Dec 2020 08:50:24 -0800 Subject: [TMA - 398 AND TMA-430] Replace Providers with Redux Store (#125) * First * WIP * Thunk * Some more comments * sc * recent searches and follounfollow * Edit profile dummy * Block / unblock and some cleanup * Replace auth provider * Sc * Delete AP after rebase * Discover users * Cleanup * More cleanup * Replace profile provider * Fixed build failure * Fixed a bug reported * Prevent app crash when backend server is down --- src/App.tsx | 11 +- src/components/comments/AddComment.tsx | 13 +- src/components/comments/CommentTile.tsx | 9 +- src/components/comments/CommentsCount.tsx | 9 +- src/components/common/AvatarTitle.tsx | 5 +- src/components/common/TaggLoadingIndicator.tsx | 27 +++ src/components/common/index.ts | 2 +- src/components/common/post/Post.tsx | 28 --- src/components/common/post/PostHeader.tsx | 78 ------ src/components/common/post/index.ts | 1 - src/components/moments/Moment.tsx | 20 +- src/components/moments/MomentTile.tsx | 23 +- src/components/profile/Avatar.tsx | 18 +- src/components/profile/Content.tsx | 167 +++++++------ src/components/profile/Cover.tsx | 19 +- src/components/profile/FollowCount.tsx | 34 ++- src/components/profile/Followers.tsx | 10 +- src/components/profile/MoreInfoDrawer.tsx | 6 +- src/components/profile/ProfileBody.tsx | 23 +- src/components/profile/ProfileHeader.tsx | 48 ++-- src/components/profile/ProfilePreview.tsx | 30 ++- src/components/search/DiscoverUsers.tsx | 19 +- src/components/search/RecentSearches.tsx | 13 +- src/components/search/SearchResults.tsx | 14 +- src/components/taggs/Tagg.tsx | 41 ++-- src/components/taggs/TaggsBar.tsx | 61 +++-- src/routes/Routes.tsx | 24 +- src/routes/authentication/AuthProvider.tsx | 315 ------------------------- src/routes/authentication/index.ts | 2 - src/routes/index.ts | 4 - src/routes/profile/Profile.tsx | 38 ++- src/routes/profile/ProfileStack.tsx | 36 +-- src/routes/tabs/NavigationBar.tsx | 5 +- src/routes/viewProfile/ProfileProvider.tsx | 207 ---------------- src/routes/viewProfile/index.ts | 2 - src/screens/onboarding/Checkpoint.tsx | 1 - src/screens/onboarding/Login.tsx | 15 +- src/screens/onboarding/SocialMedia.tsx | 14 +- src/screens/profile/CaptionScreen.tsx | 26 +- src/screens/profile/EditProfile.tsx | 13 +- src/screens/profile/FollowersListScreen.tsx | 21 +- src/screens/profile/IndividualMoment.tsx | 25 +- src/screens/profile/MomentCommentsScreen.tsx | 15 +- src/screens/profile/ProfileScreen.tsx | 74 +++--- src/screens/profile/SocialMediaTaggs.tsx | 100 +++++--- src/screens/search/SearchScreen.tsx | 37 ++- src/services/BlockUserService.ts | 8 +- src/services/ExploreServices.ts | 7 +- src/services/MomentServices.ts | 31 ++- src/services/UserFollowServices.ts | 18 +- src/services/UserProfileService.ts | 60 +---- src/store/actions/index.ts | 7 + src/store/actions/socials.ts | 38 +++ src/store/actions/taggUsers.ts | 24 ++ src/store/actions/user.ts | 52 ++++ src/store/actions/userBlock.ts | 48 ++++ src/store/actions/userFollow.ts | 57 +++++ src/store/actions/userMoments.ts | 22 ++ src/store/actions/userX.ts | 131 ++++++++++ src/store/configureStore.ts | 20 ++ src/store/initialStates.ts | 95 ++++++++ src/store/reducers/index.ts | 7 + src/store/reducers/taggUsersReducer.ts | 16 ++ src/store/reducers/userBlockReducer.ts | 25 ++ src/store/reducers/userFollowReducer.ts | 27 +++ src/store/reducers/userMomentsReducer.ts | 15 ++ src/store/reducers/userReducer.ts | 36 +++ src/store/reducers/userSocialsReducer.ts | 21 ++ src/store/reducers/userXReducer.ts | 77 ++++++ src/store/rootReducer.ts | 30 +++ src/types/types.ts | 23 ++ src/utils/hooks.ts | 31 +++ src/utils/index.ts | 1 + src/utils/users.ts | 97 ++++++++ 74 files changed, 1612 insertions(+), 1115 deletions(-) create mode 100644 src/components/common/TaggLoadingIndicator.tsx delete mode 100644 src/components/common/post/Post.tsx delete mode 100644 src/components/common/post/PostHeader.tsx delete mode 100644 src/components/common/post/index.ts delete mode 100644 src/routes/authentication/AuthProvider.tsx delete mode 100644 src/routes/authentication/index.ts delete mode 100644 src/routes/viewProfile/ProfileProvider.tsx delete mode 100644 src/routes/viewProfile/index.ts create mode 100644 src/store/actions/index.ts create mode 100644 src/store/actions/socials.ts create mode 100644 src/store/actions/taggUsers.ts create mode 100644 src/store/actions/user.ts create mode 100644 src/store/actions/userBlock.ts create mode 100644 src/store/actions/userFollow.ts create mode 100644 src/store/actions/userMoments.ts create mode 100644 src/store/actions/userX.ts create mode 100644 src/store/configureStore.ts create mode 100644 src/store/initialStates.ts create mode 100644 src/store/reducers/index.ts create mode 100644 src/store/reducers/taggUsersReducer.ts create mode 100644 src/store/reducers/userBlockReducer.ts create mode 100644 src/store/reducers/userFollowReducer.ts create mode 100644 src/store/reducers/userMomentsReducer.ts create mode 100644 src/store/reducers/userReducer.ts create mode 100644 src/store/reducers/userSocialsReducer.ts create mode 100644 src/store/reducers/userXReducer.ts create mode 100644 src/store/rootReducer.ts create mode 100644 src/utils/hooks.ts create mode 100644 src/utils/users.ts (limited to 'src') diff --git a/src/App.tsx b/src/App.tsx index 2e6865fd..e1cd83cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,19 @@ import React from 'react'; import {NavigationContainer} from '@react-navigation/native'; -import Routes, {AuthProvider} from './routes'; +import Routes from './routes'; +import {Provider} from 'react-redux'; +import store from './store/configureStore'; const App = () => { return ( - + /** + * This is the provider from the redux store, it acts as the root provider for our application + */ + - + ); }; diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index ac1628da..f8c0b6bc 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -7,9 +7,11 @@ import { View, } from 'react-native'; import AsyncStorage from '@react-native-community/async-storage'; -import {AuthContext} from '../../routes'; import {TaggBigInput} from '../onboarding'; import {postMomentComment} from '../../services'; +import {logout} from '../../store/actions'; +import {useSelector, useDispatch} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; /** * This file provides the add comment view for a user. @@ -27,11 +29,12 @@ const AddComment: React.FC = ({ moment_id, }) => { const [comment, setComment] = React.useState(''); + + const dispatch = useDispatch(); const { avatar, - user: {userId, username}, - logout, - } = React.useContext(AuthContext); + user: {userId}, + } = useSelector((state: RootState) => state.user); const handleCommentUpdate = (comment: string) => { setComment(comment); @@ -41,7 +44,7 @@ const AddComment: React.FC = ({ try { const token = await AsyncStorage.getItem('token'); if (!token) { - logout(); + dispatch(logout()); return; } const postedComment = await postMomentComment( diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index bee590f5..da78a4dc 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Text, View} from 'react-native-animatable'; import {ProfilePreview} from '../profile'; -import {CommentType} from '../../types'; +import {CommentType, ScreenType} from '../../types'; import {StyleSheet} from 'react-native'; import {getTimePosted} from '../../utils'; import ClockIcon from '../../assets/icons/clock-icon-01.svg'; @@ -12,9 +12,13 @@ import ClockIcon from '../../assets/icons/clock-icon-01.svg'; interface CommentTileProps { comment_object: CommentType; + screenType: ScreenType; } -const CommentTile: React.FC = ({comment_object}) => { +const CommentTile: React.FC = ({ + comment_object, + screenType, +}) => { const timePosted = getTimePosted(comment_object.date_time); return ( @@ -26,6 +30,7 @@ const CommentTile: React.FC = ({comment_object}) => { last_name: '', }} previewType={'Comment'} + screenType={screenType} /> {comment_object.comment} diff --git a/src/components/comments/CommentsCount.tsx b/src/components/comments/CommentsCount.tsx index a9d5b6d6..d210c39a 100644 --- a/src/components/comments/CommentsCount.tsx +++ b/src/components/comments/CommentsCount.tsx @@ -3,6 +3,7 @@ import {Text} from 'react-native-animatable'; import {StyleSheet, TouchableOpacity} from 'react-native'; import CommentIcon from '../../assets/icons/moment-comment-icon.svg'; import {useNavigation} from '@react-navigation/native'; +import {ScreenType} from '../../types'; /** * Provides a view for the comment icon and the comment count. @@ -11,20 +12,20 @@ import {useNavigation} from '@react-navigation/native'; type CommentsCountProps = { comments_count: string; - isProfileView: boolean; moment_id: string; + screenType: ScreenType; }; const CommentsCount: React.FC = ({ comments_count, - isProfileView, moment_id, + screenType, }) => { const navigation = useNavigation(); const navigateToCommentsScreen = async () => { navigation.push('MomentCommentsScreen', { - isProfileView: isProfileView, - moment_id: moment_id, + moment_id, + screenType, }); }; return ( diff --git a/src/components/common/AvatarTitle.tsx b/src/components/common/AvatarTitle.tsx index 65ae7486..a38f46fa 100644 --- a/src/components/common/AvatarTitle.tsx +++ b/src/components/common/AvatarTitle.tsx @@ -2,12 +2,9 @@ import React from 'react'; import {Image, StyleSheet, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {TAGGS_GRADIENT} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; -import {loadAvatar} from '../../services'; -import AsyncStorage from '@react-native-community/async-storage'; type AvatarTitleProps = { - avatar: string; + avatar: string | null; }; const AvatarTitle: React.FC = ({avatar}) => { return ( diff --git a/src/components/common/TaggLoadingIndicator.tsx b/src/components/common/TaggLoadingIndicator.tsx new file mode 100644 index 00000000..cfb99e80 --- /dev/null +++ b/src/components/common/TaggLoadingIndicator.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {ActivityIndicator, StyleSheet, View} from 'react-native'; + +type TaggLoadingIndicatorProps = { + color: string; +}; +const TaggLoadingIndicator: React.FC = ({color}) => { + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + }, + horizontal: { + flexDirection: 'row', + justifyContent: 'space-around', + padding: 10, + }, +}); + +export default TaggLoadingIndicator; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 0feeaab8..f6521497 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -15,4 +15,4 @@ 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'; +export {default as TaggLoadingTndicator} from './TaggLoadingIndicator'; diff --git a/src/components/common/post/Post.tsx b/src/components/common/post/Post.tsx deleted file mode 100644 index 9fa167f2..00000000 --- a/src/components/common/post/Post.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import {StyleSheet, View, Image} from 'react-native'; -import {PostType} from '../../../types'; -import PostHeader from './PostHeader'; -import {SCREEN_WIDTH} from '../../../utils'; - -interface PostProps { - post: PostType; -} -const Post: React.FC = ({post: {owner, social, data}}) => { - return ( - <> - - - {data && } - - - ); -}; - -const styles = StyleSheet.create({ - image: { - width: SCREEN_WIDTH, - height: SCREEN_WIDTH, - backgroundColor: '#eee', - }, -}); -export default Post; diff --git a/src/components/common/post/PostHeader.tsx b/src/components/common/post/PostHeader.tsx deleted file mode 100644 index 0e9c708b..00000000 --- a/src/components/common/post/PostHeader.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import {UserType, InstagramPostType} from '../../../types'; -import {View, StyleSheet, Image, Text} from 'react-native'; -import {AuthContext} from '../../../routes/authentication'; -import SocialIcon from '../SocialIcon'; -import moment from 'moment'; - -const AVATAR_DIM = 35; -interface PostHeaderProps { - owner: UserType; - post: InstagramPostType | undefined; - social: string; -} -const PostHeader: React.FC = ({ - owner: {username}, - post, - social, -}) => { - const {avatar} = React.useContext(AuthContext); - - return ( - - - - {username} - {post && } - - {post && ( - - {moment(post.timestamp).format('LL')} at{' '} - {moment(post.timestamp).format('LT')} - - )} - - ); -}; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'column', - justifyContent: 'space-between', - padding: 10, - backgroundColor: 'white', - }, - topRow: { - flexDirection: 'row', - alignItems: 'center', - }, - avatar: { - width: AVATAR_DIM, - height: AVATAR_DIM, - borderRadius: AVATAR_DIM / 2, - marginRight: 10, - }, - icon: { - width: AVATAR_DIM, - height: AVATAR_DIM, - borderRadius: AVATAR_DIM / 2, - marginLeft: '55%', - }, - username: { - fontSize: 18, - }, - timestamp: { - color: '#6A757D', - fontSize: 11, - marginLeft: AVATAR_DIM + 10, - }, -}); - -export default PostHeader; diff --git a/src/components/common/post/index.ts b/src/components/common/post/index.ts deleted file mode 100644 index 358a59d5..00000000 --- a/src/components/common/post/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {default as Post} from './Post'; diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index 9e138ef3..0c8febcf 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -10,15 +10,21 @@ import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {SCREEN_WIDTH} from '../../utils'; import ImagePicker from 'react-native-image-crop-picker'; import MomentTile from './MomentTile'; -import {MomentType} from 'src/types'; +import {MomentType, ScreenType} from 'src/types'; interface MomentProps { title: string; images: MomentType[] | undefined; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Moment: React.FC = ({title, images, isProfileView}) => { +const Moment: React.FC = ({ + title, + images, + userXId, + screenType, +}) => { const navigation = useNavigation(); const navigateToImagePicker = () => { @@ -32,6 +38,7 @@ const Moment: React.FC = ({title, images, isProfileView}) => { .then((picture) => { if ('path' in picture) { navigation.navigate('CaptionScreen', { + screenType, title: title, image: picture, }); @@ -45,7 +52,7 @@ const Moment: React.FC = ({title, images, isProfileView}) => { {title} - {!isProfileView ? ( + {!userXId ? ( = ({title, images, isProfileView}) => { ))} - {(images === undefined || images.length === 0) && !isProfileView && ( + {(images === undefined || images.length === 0) && !userXId && ( navigateToImagePicker()}> diff --git a/src/components/moments/MomentTile.tsx b/src/components/moments/MomentTile.tsx index 787957e0..cc24c531 100644 --- a/src/components/moments/MomentTile.tsx +++ b/src/components/moments/MomentTile.tsx @@ -1,26 +1,29 @@ import {useNavigation} from '@react-navigation/native'; import React from 'react'; import {StyleSheet, View, Image, TouchableOpacity} from 'react-native'; -import {MomentType} from 'src/types'; -import {ProfileContext} from '../../routes'; +import {MomentType, ScreenType} from 'src/types'; interface MomentTileProps { moment: MomentType; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const MomentTile: React.FC = ({moment, isProfileView}) => { +const MomentTile: React.FC = ({ + moment, + userXId, + screenType, +}) => { const navigation = useNavigation(); - //Username is needed by the IndividualMoment screen - const { - user: {username}, - } = React.useContext(ProfileContext); - const {path_hash} = moment; return ( { - navigation.push('IndividualMoment', {moment, isProfileView, username}); + navigation.push('IndividualMoment', { + moment, + screenType, + userXId, + }); }}> diff --git a/src/components/profile/Avatar.tsx b/src/components/profile/Avatar.tsx index aca3bf4d..903d0d18 100644 --- a/src/components/profile/Avatar.tsx +++ b/src/components/profile/Avatar.tsx @@ -1,16 +1,20 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {Image, StyleSheet} from 'react-native'; -import {AuthContext, ProfileContext} from '../../routes/'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; const PROFILE_DIM = 100; interface AvatarProps { style: object; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Avatar: React.FC = ({style, isProfileView}) => { - const {avatar} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const Avatar: React.FC = ({style, screenType, userXId}) => { + const {avatar} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + return ( ; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Content: React.FC = ({y, isProfileView}) => { - const [profileBodyHeight, setProfileBodyHeight] = useState(0); - const {user, moments, followers, following, updateFollowers} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); - - const { - logout, - user: loggedInUser, - updateFollowers: updateLoggedInUserFollowers, - blockedUsers, - updateBlockedUsers, - } = React.useContext(AuthContext); +const Content: React.FC = ({y, userXId, screenType}) => { + const dispatch = useDispatch(); + + const {user = NO_USER, profile = NO_PROFILE} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + + const {followers = EMPTY_PROFILE_PREVIEW_LIST} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.follow); + + const {moments = EMPTY_MOMENTS_LIST} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.moments); + + const {blockedUsers = EMPTY_PROFILE_PREVIEW_LIST} = useSelector( + (state: RootState) => state.blocked, + ); + const {user: loggedInUser = NO_USER} = useSelector( + (state: RootState) => state.user, + ); + const state = useStore().getState(); /** * States @@ -38,8 +65,9 @@ const Content: React.FC = ({y, isProfileView}) => { const [imagesMap, setImagesMap] = useState>( new Map(), ); - const [isFollowed, setIsFollowed] = React.useState(false); - const [isBlocked, setIsBlocked] = React.useState(false); + const [isFollowed, setIsFollowed] = useState(false); + const [isBlocked, setIsBlocked] = useState(false); + const [profileBodyHeight, setProfileBodyHeight] = useState(0); /** * If own profile is being viewed then do not show the follow button. @@ -51,8 +79,6 @@ const Content: React.FC = ({y, isProfileView}) => { setProfileBodyHeight(height); }; - const {userId} = user; - const createImagesMap = useCallback(() => { var map = new Map(); moments.forEach(function (imageObject) { @@ -68,9 +94,6 @@ const Content: React.FC = ({y, isProfileView}) => { }, [moments]); useEffect(() => { - if (!userId) { - return; - } createImagesMap(); }, [createImagesMap]); @@ -78,9 +101,6 @@ const Content: React.FC = ({y, isProfileView}) => { * This hook is called on load of profile and when you update the followers list. */ useEffect(() => { - if (!userId) { - return; - } const isActuallyFollowed = followers.some( (follower) => follower.username === loggedInUser.username, ); @@ -90,62 +110,63 @@ const Content: React.FC = ({y, isProfileView}) => { }, [followers]); useEffect(() => { - if (!userId) { - return; - } - const isActuallyBlocked = blockedUsers.some( (cur_user) => user.username === cur_user.username, ); if (isBlocked != isActuallyBlocked) { setIsBlocked(isActuallyBlocked); } - }, [blockedUsers]); + }, [blockedUsers, user]); + + /** + * The object returned by this method is added to the list of blocked / followed users by the reducer. + * Which helps us prevent an extra api call to the backend just to fetch a user. + */ + const getUserAsProfilePreviewType = ( + passedInUser: UserType, + passedInProfile: ProfileType, + ): ProfilePreviewType => { + const fullName = passedInProfile.name.split(' '); + return { + id: passedInUser.userId, + username: passedInUser.username, + first_name: fullName[0], + last_name: fullName[1], + }; + }; /** * Handles a click on the follow / unfollow button. - * updateFollowers and updateLoggedInUerFollowers to make sure that we update followers list / count for both the users in context. + * followUnfollowUser takes care of updating the following list for loggedInUser + * updateUserXFollowersAndFollowing updates followers and following list for the followed user. */ + const handleFollowUnfollow = async () => { - const token = await AsyncStorage.getItem('token'); - if (!token) { - logout(); - return; - } - const isUpdatedSuccessful = await followOrUnfollowUser( - loggedInUser.userId, - userId, - token, - isFollowed, + await dispatch( + followUnfollowUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + isFollowed, + ), ); - if (isUpdatedSuccessful) { - setIsFollowed(!isFollowed); - updateFollowers(true); - updateLoggedInUserFollowers(true); - } + await dispatch(updateUserXFollowersAndFollowing(user.userId, state)); }; /** * Handles a click on the block / unblock button. + * loadFollowData updates followers / following list for the logged in user + * updateUserXFollowersAndFollowing updates followers and following list for the followed user. */ const handleBlockUnblock = async () => { - const token = await AsyncStorage.getItem('token'); - if (!token) { - logout(); - return; - } - const isUpdatedSuccessful = await blockOrUnblockUser( - loggedInUser.userId, - userId, - token, - isBlocked, + await dispatch( + blockUnblockUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + isBlocked, + ), ); - if (isUpdatedSuccessful) { - setIsBlocked(!isBlocked); - updateBlockedUsers(true); - updateFollowers(true); - updateLoggedInUserFollowers(true); - } + await dispatch(loadFollowData(loggedInUser.userId)); + await dispatch(updateUserXFollowersAndFollowing(user.userId, state)); }; return ( @@ -155,15 +176,12 @@ const Content: React.FC = ({y, isProfileView}) => { showsVerticalScrollIndicator={false} scrollEventThrottle={1}> - + = ({y, isProfileView}) => { handleBlockUnblock, }} /> - + {defaultMoments.map((title, index) => ( ))} diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx index 36e41776..3c0f7045 100644 --- a/src/components/profile/Cover.tsx +++ b/src/components/profile/Cover.tsx @@ -1,18 +1,23 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {Image, StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; import {IMAGE_WIDTH, COVER_HEIGHT, IMAGE_HEIGHT} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; +import {useSelector, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; +import {DUMMY_USERID, NO_USER_DATA} from '../../store/initialStates'; const {interpolate, Extrapolate} = Animated; interface CoverProps { y: Animated.Value; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Cover: React.FC = ({y, isProfileView}) => { - const {cover} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const Cover: React.FC = ({y, userXId, screenType}) => { + const {cover = ''} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + const scale: Animated.Node = interpolate(y, { inputRange: [-COVER_HEIGHT, 0], outputRange: [1.5, 1.25], diff --git a/src/components/profile/FollowCount.tsx b/src/components/profile/FollowCount.tsx index 3e270428..a23a3533 100644 --- a/src/components/profile/FollowCount.tsx +++ b/src/components/profile/FollowCount.tsx @@ -1,24 +1,33 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {View, Text, StyleSheet, ViewProps} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {useNavigation} from '@react-navigation/native'; -import {AuthContext, ProfileContext} from '../../routes'; +import {RootState} from '../../store/rootReducer'; +import {useSelector} from 'react-redux'; +import {ScreenType} from '../../types'; +import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates'; interface FollowCountProps extends ViewProps { mode: 'followers' | 'following'; - count: number; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } const FollowCount: React.FC = ({ style, mode, - count, - isProfileView, + userXId, + screenType, }) => { - const {followers, following} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); + const { + followers = EMPTY_PROFILE_PREVIEW_LIST, + following = EMPTY_PROFILE_PREVIEW_LIST, + } = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.follow); + + const isFollowers = mode === 'followers'; + const count = isFollowers ? followers.length : following.length; const navigation = useNavigation(); const displayed: string = @@ -33,14 +42,15 @@ const FollowCount: React.FC = ({ navigation.push('FollowersListScreen', { - isFollowers: mode === 'followers', - list: mode === 'followers' ? followers : following, + isFollowers, + userXId, + screenType, }) }> {displayed} - {mode === 'followers' ? 'Followers' : 'Following'} + {isFollowers ? 'Followers' : 'Following'} diff --git a/src/components/profile/Followers.tsx b/src/components/profile/Followers.tsx index e11041d0..c665603d 100644 --- a/src/components/profile/Followers.tsx +++ b/src/components/profile/Followers.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View, StyleSheet, ViewProps, Text} from 'react-native'; -import {ProfilePreviewType} from '../../types'; +import {ProfilePreviewType, ScreenType} from '../../types'; import {ProfilePreview} from '..'; import {useNavigation} from '@react-navigation/native'; import {Button} from 'react-native-elements'; @@ -8,9 +8,14 @@ import {Button} from 'react-native-elements'; interface FollowersListProps { result: Array; sectionTitle: string; + screenType: ScreenType; } -const Followers: React.FC = ({result, sectionTitle}) => { +const Followers: React.FC = ({ + result, + sectionTitle, + screenType, +}) => { const navigation = useNavigation(); return ( <> @@ -31,6 +36,7 @@ const Followers: React.FC = ({result, sectionTitle}) => { key={profilePreview.id} {...{profilePreview}} previewType={'Comment'} + screenType={screenType} /> ))} diff --git a/src/components/profile/MoreInfoDrawer.tsx b/src/components/profile/MoreInfoDrawer.tsx index 719c1894..a8908b4d 100644 --- a/src/components/profile/MoreInfoDrawer.tsx +++ b/src/components/profile/MoreInfoDrawer.tsx @@ -4,14 +4,14 @@ 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'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; interface MoreInfoDrawerProps { isOpen: boolean; setIsOpen: (visible: boolean) => void; - isProfileView: boolean; } const MoreInfoDrawer: React.FC = (props) => { @@ -20,7 +20,7 @@ const MoreInfoDrawer: React.FC = (props) => { const navigation = useNavigation(); const { user: {userId, username}, - } = useContext(AuthContext); + } = useSelector((state: RootState) => state.user); const goToEditProfile = () => { navigation.push('EditProfile', { diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index c0253533..3c05fc26 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,33 +1,38 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; import ToggleButton from './ToggleButton'; +import {RootState} from '../../store/rootReducer'; +import {useSelector} from 'react-redux'; +import {ScreenType} from '../../types'; +import {NO_PROFILE} from '../../store/initialStates'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; - isProfileView: boolean; isFollowed: boolean; isBlocked: boolean; isOwnProfile: boolean; handleFollowUnfollow: Function; handleBlockUnblock: Function; + userXId: string; + screenType: ScreenType; } const ProfileBody: React.FC = ({ onLayout, - isProfileView, isFollowed, isBlocked, isOwnProfile, handleFollowUnfollow, handleBlockUnblock, + userXId, + screenType, }) => { const { - profile, + profile = NO_PROFILE, user: {username}, - } = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); + } = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); const {biography, website} = profile; @@ -36,7 +41,7 @@ const ProfileBody: React.FC = ({ {`@${username}`} {`${biography}`} {`${website}`} - {isProfileView && !isOwnProfile ? ( + {userXId && !isOwnProfile ? ( {!isBlocked && ( = ({ - isProfileView, - numFollowing, - numFollowers, -}) => { - const { - profile: {name}, - } = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const ProfileHeader: React.FC = ({userXId, screenType}) => { + const {profile: {name = ''} = {}} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + const [drawerVisible, setDrawerVisible] = useState(false); return ( - {!isProfileView && ( + {!userXId && ( <> = ({ }}> - + )} - + {name} diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index af116dd3..5567fa5a 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState, useContext} from 'react'; -import {ProfilePreviewType} from '../../types'; +import {ProfilePreviewType, ScreenType} from '../../types'; import { View, Text, @@ -14,8 +14,11 @@ import RNFetchBlob from 'rn-fetch-blob'; import AsyncStorage from '@react-native-community/async-storage'; import {AVATAR_PHOTO_ENDPOINT} from '../../constants'; import {UserType, PreviewType} from '../../types'; -import {AuthContext} from '../../routes/'; import {isUserBlocked} from '../../services'; +import {useSelector, useDispatch, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {loadUserX, logout} from '../../store/actions'; +import {userXInStore} from '../../utils'; const NO_USER: UserType = { userId: '', username: '', @@ -27,22 +30,24 @@ const NO_USER: UserType = { * If isComment is true then it means that we are not displaying this tile as a part of search results. * And hence we do not cache the search results. * On the other hand, if isComment is false, then we should update the search cache. (This cache needs to be revamped to clear outdated results.) - * In either case, we update the userBeingVisited in our AuthContext (Which can be used to make api calls later on to fetch user specific data). * Finally, We navigate to Profile. */ interface ProfilePreviewProps extends ViewProps { profilePreview: ProfilePreviewType; previewType: PreviewType; + screenType: ScreenType; } const ProfilePreview: React.FC = ({ profilePreview: {username, first_name, last_name, id}, previewType, + screenType, }) => { const navigation = useNavigation(); - const {user: loggedInUser, logout} = React.useContext(AuthContext); + const {user: loggedInUser} = useSelector((state: RootState) => state.user); const [avatarURI, setAvatarURI] = useState(null); const [user, setUser] = useState(NO_USER); + const dispatch = useDispatch(); useEffect(() => { let mounted = true; const loadAvatar = async () => { @@ -87,12 +92,14 @@ const ProfilePreview: React.FC = ({ const checkIfUserIsBlocked = async (userId: string) => { const token = await AsyncStorage.getItem('token'); if (!token) { - logout(); + dispatch(logout()); return false; } return await isUserBlocked(userId, loggedInUser.userId, token); }; + const state: RootState = useStore().getState(); + const addToRecentlyStoredAndNavigateToProfile = async () => { let user: ProfilePreviewType = { id, @@ -145,12 +152,19 @@ const ProfilePreview: React.FC = ({ } /** - * Navigate to profile of the user selected + * Dispatch an event to Fetch the user details + * If the user is already present in store, do not fetch again + * Finally, Navigate to profile of the user selected */ + if (!userXInStore(state, screenType, user.id)) { + dispatch( + loadUserX({userId: user.id, username: user.username}, screenType), + ); + } navigation.push('Profile', { - isProfileView: true, username: user.username, - userId: user.id, + userXId: user.id, + screenType: screenType, }); } catch (e) { console.log(e); diff --git a/src/components/search/DiscoverUsers.tsx b/src/components/search/DiscoverUsers.tsx index 885c712b..ec0a8daa 100644 --- a/src/components/search/DiscoverUsers.tsx +++ b/src/components/search/DiscoverUsers.tsx @@ -6,23 +6,32 @@ import { StyleSheet, TouchableOpacityProps, } from 'react-native'; -import {ProfilePreviewType} from '../../types'; +import {PreviewType, ProfilePreviewType, ScreenType} from '../../types'; import SearchResults from './SearchResults'; interface DiscoverUsersProps extends TouchableOpacityProps { - sectionTitle: string; + sectionTitle: PreviewType; users: Array; + screenType: ScreenType; } /** * An image component that returns the of the icon for a specific social media platform. */ -const DiscoverUsers: React.FC = (props) => { +const DiscoverUsers: React.FC = ({ + sectionTitle, + screenType, + users, +}) => { return ( - {props.sectionTitle} + {sectionTitle} - + ); }; diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx index 6a98e49a..22a36a6b 100644 --- a/src/components/search/RecentSearches.tsx +++ b/src/components/search/RecentSearches.tsx @@ -6,14 +6,15 @@ import { StyleSheet, TouchableOpacityProps, } from 'react-native'; -import {ProfilePreviewType} from 'src/types'; -import { TAGG_TEXT_LIGHT_BLUE } from '../../constants'; +import {PreviewType, ProfilePreviewType, ScreenType} from 'src/types'; +import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import SearchResults from './SearchResults'; interface RecentSearchesProps extends TouchableOpacityProps { - sectionTitle: string; + sectionTitle: PreviewType; sectionButtonTitle: string; recents: Array; + screenType: ScreenType; } /** * An image component that returns the of the icon for a specific social media platform. @@ -29,7 +30,11 @@ const RecentSearches: React.FC = (props) => { )} - + ); }; diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index 2d5c9db8..001c7968 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -1,20 +1,26 @@ import React from 'react'; -import {ProfilePreviewType, PreviewType} from '../../types'; +import {ProfilePreviewType, PreviewType, ScreenType} from '../../types'; import ProfilePreview from '../profile/ProfilePreview'; import {StyleSheet, View} from 'react-native'; interface SearchResultsProps { results: Array; previewType: PreviewType; + screenType: ScreenType; } -const SearchResults: React.FC = (props) => { +const SearchResults: React.FC = ({ + results, + previewType, + screenType, +}) => { return ( - {props.results.map((profilePreview) => ( + {results.map((profilePreview) => ( ))} diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx index 9f8fafd1..086b3c87 100644 --- a/src/components/taggs/Tagg.tsx +++ b/src/components/taggs/Tagg.tsx @@ -1,5 +1,5 @@ import {useNavigation} from '@react-navigation/native'; -import React, {Fragment, useContext, useState} from 'react'; +import React, {Fragment, useContext, useEffect, useState} from 'react'; import {Alert, Linking, StyleSheet, TouchableOpacity, View} from 'react-native'; import PurpleRingPlus from '../../assets/icons/purple_ring+.svg'; import PurpleRing from '../../assets/icons/purple_ring.svg'; @@ -17,36 +17,35 @@ import { registerNonIntegratedSocialLink, } from '../../services'; import {SmallSocialIcon, SocialIcon, SocialLinkModal} from '../common'; -import {AuthContext, ProfileContext} from '../../routes'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; interface TaggProps { social: string; - isProfileView: boolean; isLinked: boolean; isIntegrated: boolean; setTaggsNeedUpdate: (_: boolean) => void; - setSocialDataNeedUpdate: (_: string[]) => void; - userId: string; + setSocialDataNeedUpdate: (_: string) => void; + userXId: string; + screenType: ScreenType; } const Tagg: React.FC = ({ social, - isProfileView, isLinked, isIntegrated, setTaggsNeedUpdate, setSocialDataNeedUpdate, - userId, + userXId, + screenType, }) => { const navigation = useNavigation(); const [modalVisible, setModalVisible] = useState(false); - const youMayPass = isLinked || isProfileView; - const { - profile: {name}, - socialAccounts, - avatar, - } = isProfileView ? useContext(ProfileContext) : useContext(AuthContext); - + const youMayPass = isLinked || userXId; + const {user} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); /* case isProfileView: case linked: @@ -62,7 +61,7 @@ const Tagg: React.FC = ({ show auth browser case !integrated_social: show modal - Tagg's "Tagg" will use the Ring instead of PurpleRing + Tagg's "Tagg" will use the Ring instead of PurpleRing */ const modalOrAuthBrowserOrPass = async () => { @@ -70,14 +69,10 @@ const Tagg: React.FC = ({ if (INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1) { navigation.push('SocialMediaTaggs', { socialMediaType: social, - isProfileView: isProfileView, - userId: userId, - name: name, - accountData: socialAccounts[social], - avatar: avatar, + userXId, }); } else { - getNonIntegratedURL(social, userId).then((socialURL) => { + getNonIntegratedURL(social, user.userId).then((socialURL) => { if (socialURL) { Linking.openURL(socialURL); } else { @@ -89,7 +84,7 @@ const Tagg: React.FC = ({ if (isIntegrated) { handlePressForAuthBrowser(social).then((success) => { setTaggsNeedUpdate(success); - setSocialDataNeedUpdate(success ? [social] : []); + if (success) setSocialDataNeedUpdate(social); }); } else { setModalVisible(true); @@ -127,7 +122,7 @@ const Tagg: React.FC = ({ return ( <> - {isProfileView && !isLinked ? ( + {userXId && !isLinked ? ( ) : ( <> diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index aac68e99..12e4b93a 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -1,35 +1,53 @@ -// @refresh react -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useState, useContext} from 'react'; import {StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; +import {useDispatch, useSelector} from 'react-redux'; import { INTEGRATED_SOCIAL_LIST, PROFILE_CUTOUT_BOTTOM_Y, SOCIAL_LIST, } from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes'; import {getLinkedSocials} from '../../services'; import {StatusBarHeight} from '../../utils'; import Tagg from './Tagg'; +import {RootState} from '../../store/rootReducer'; +import {ScreenType} from '../../types'; +import {loadIndividualSocial} from '../../store/actions'; const {View, ScrollView, interpolate, Extrapolate} = Animated; interface TaggsBarProps { y: Animated.Value; profileBodyHeight: number; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } const TaggsBar: React.FC = ({ y, profileBodyHeight, - isProfileView, + userXId, + screenType, }) => { let [taggs, setTaggs] = useState([]); let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true); - const context = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); - const {user, socialsNeedUpdate} = context; + const {user} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + + const dispatch = useDispatch(); + + /** + * Updates the individual social that needs update + * @param socialType Type of the social that needs update + */ + const handleSocialUpdate = (socialType: string) => { + dispatch(loadIndividualSocial(user.userId, socialType)); + }; + + /** + * This useEffect should be called evey time the user being viewed is changed OR + * And update is triggered manually + */ useEffect(() => { const loadData = async () => { getLinkedSocials(user.userId).then((linkedSocials) => { @@ -43,12 +61,12 @@ const TaggsBar: React.FC = ({ , ); i++; @@ -58,12 +76,12 @@ const TaggsBar: React.FC = ({ , ); i++; @@ -73,17 +91,8 @@ const TaggsBar: React.FC = ({ }); }; - if (taggsNeedUpdate) { - /** - * Triggering redundant call to the backend for now to make the app work. - * TODO : Figure out a better way to get the updates social posts for the profile being visited. - * Have an event triggered from ProfileProvider based on which we could make a call to backedn to get updated posts. - */ - //We may need the line below in future ? - // socialsNeedUpdate(INTEGRATED_SOCIAL_LIST); - loadData(); - } - }, [isProfileView, taggsNeedUpdate, user.userId]); + loadData(); + }, [taggsNeedUpdate, user]); const shadowOpacity: Animated.Node = interpolate(y, { inputRange: [ diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx index 92cd3dd2..e54f038d 100644 --- a/src/routes/Routes.tsx +++ b/src/routes/Routes.tsx @@ -1,13 +1,29 @@ -import React from 'react'; - -import {AuthContext} from './authentication'; +import React, {useEffect} from 'react'; import NavigationBar from './tabs'; import Onboarding from './onboarding'; +import {useSelector, useDispatch} from 'react-redux'; +import {RootState} from '../store/rootReducer'; +import {userLogin} from '../utils'; const Routes: React.FC = () => { const { user: {userId}, - } = React.useContext(AuthContext); + } = useSelector((state: RootState) => state.user); + const dispatch = useDispatch(); + + /** + * Load the user from AsyncStorage if any + * Note that this makes logout triggered by invalid Token have no effect. + * We should figure out a way to handle that. + * Suggestions? + * NOTE : Not something introduced by this commit but something we already have. + */ + useEffect(() => { + if (!userId) { + userLogin(dispatch, {userId: '', username: ''}); + } + }, [userId, userLogin]); + return userId ? : ; }; diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx deleted file mode 100644 index 46f761e1..00000000 --- a/src/routes/authentication/AuthProvider.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import AsyncStorage from '@react-native-community/async-storage'; -import React, {createContext, useEffect, useState} from 'react'; -import {INTEGRATED_SOCIAL_LIST} from '../../constants'; -import { - loadAvatar, - loadCover, - loadFollowers, - loadFollowing, - loadMoments, - loadProfileInfo, - loadRecentlySearchedUsers, - loadSocialPosts, - getAllTaggUsers, - loadBlockedUsers, -} from '../../services'; -import { - MomentType, - ProfilePreviewType, - ProfileType, - SocialAccountType, - UserType, -} from '../../types'; - -interface AuthContextProps { - user: UserType; - profile: ProfileType; - login: (userId: string, username: string) => void; - logout: () => void; - avatar: string | null; - cover: string | null; - socialAccounts: Record; - recentSearches: Array; - taggUsers: Array; - newMomentsAvailable: boolean; - updateMoments: (value: boolean) => void; - socialsNeedUpdate: (_: string[]) => void; - moments: MomentType[]; - followers: ProfilePreviewType[]; - following: ProfilePreviewType[]; - followersNeedUpdate: boolean; - updateFollowers: (value: boolean) => void; - blockedUsers: ProfilePreviewType[]; - blockedUsersNeedUpdate: boolean; - updateBlockedUsers: (value: boolean) => void; - isEditedProfile: boolean; - updateIsEditedProfile: (value: boolean) => void; -} - -const NO_USER: UserType = { - userId: '', - username: '', -}; - -const NO_PROFILE: ProfileType = { - biography: '', - website: '', - name: '', - gender: '', - birthday: undefined, -}; - -const NO_SOCIAL_ACCOUNTS: Record = { - Instagram: {posts: []}, - Facebook: {posts: []}, - Twitter: {posts: []}, -}; - -export const AuthContext = createContext({ - user: NO_USER, - profile: NO_PROFILE, - login: () => {}, - logout: () => {}, - avatar: null, - cover: null, - recentSearches: [], - taggUsers: [], - newMomentsAvailable: true, - updateMoments: () => {}, - socialAccounts: NO_SOCIAL_ACCOUNTS, - socialsNeedUpdate: () => {}, - moments: [], - followers: [], - following: [], - followersNeedUpdate: true, - updateFollowers: () => {}, - blockedUsers: [], - blockedUsersNeedUpdate: true, - updateBlockedUsers: () => {}, - isEditedProfile: false, - updateIsEditedProfile: () => {}, -}); - -/** - * Authentication provider for the application. - */ -const AuthProvider: React.FC = ({children}) => { - const [user, setUser] = useState(NO_USER); - const [profile, setProfile] = useState(NO_PROFILE); - const [avatar, setAvatar] = useState(null); - const [cover, setCover] = useState(null); - const [socialAccounts, setSocialAccounts] = useState< - Record - >(NO_SOCIAL_ACCOUNTS); - const [recentSearches, setRecentSearches] = useState< - Array - >([]); - const [taggUsers, setTaggUsers] = useState>([]); - const [newMomentsAvailable, setNewMomentsAvailable] = useState(true); - // Default update all integrated social lists on start - const [socialsNeedUpdate, setSocialsNeedUpdate] = useState([ - ...INTEGRATED_SOCIAL_LIST, - ]); - const [moments, setMoments] = useState>([]); - const [followers, setFollowers] = useState>([]); - const [following, setFollowing] = useState>([]); - const [followersNeedUpdate, setFollowersNeedUpdate] = useState(true); - const [blockedUsers, setBlockedUsers] = useState>( - [], - ); - const [blockedUsersNeedUpdate, setBlockedUsersNeedUpdate] = useState( - true, - ); - const [isEditedProfile, setIsEditedProfile] = useState(false); - const {userId} = user; - - useEffect(() => { - const loadUserInfoFromStorage = async () => { - const [id, username, token] = await Promise.all([ - AsyncStorage.getItem('userId'), - AsyncStorage.getItem('username'), - AsyncStorage.getItem('token'), - ]); - if (id && username && token) { - setUser({...user, userId: id, username}); - } - }; - if (user === NO_USER) { - loadUserInfoFromStorage(); - } - }, [user]); - - useEffect(() => { - if (!userId) { - return; - } - - const loadData = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - loadProfileInfo(token, userId, setProfile); - loadAvatar(token, userId, setAvatar); - loadCover(token, userId, setCover); - loadRecentlySearchedUsers(setRecentSearches); - } catch (err) { - console.log(err); - } - }; - loadData(); - }, [userId, isEditedProfile]); - - useEffect(() => { - const loadNewMoments = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - const newMoments = await loadMoments(userId, token); - if (newMoments) { - setMoments(newMoments); - } - setNewMomentsAvailable(false); - } catch (error) { - console.log(error); - } - }; - if (newMomentsAvailable && userId) { - loadNewMoments(); - } - }, [newMomentsAvailable, userId, moments]); - - useEffect(() => { - const loadNewFollowers = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - loadFollowers(userId, token, setFollowers); - loadFollowing(userId, token, setFollowing); - setFollowersNeedUpdate(false); - } catch (error) { - console.log(error); - } - }; - if (followersNeedUpdate && userId) { - loadNewFollowers(); - } - }, [followersNeedUpdate, userId, followers, following]); - - useEffect(() => { - if (socialsNeedUpdate.length > 0 && userId) { - for (let social of socialsNeedUpdate) { - loadSocialPosts(userId, social).then((accountData) => { - socialAccounts[social] = accountData; - setSocialAccounts(socialAccounts); - console.log('Refreshed posts data:', social); - }); - } - setSocialsNeedUpdate([]); - } - }, [socialAccounts, socialsNeedUpdate, userId]); - - useEffect(() => { - const loadNewBlockedUsers = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - loadBlockedUsers(userId, token, setBlockedUsers); - setBlockedUsersNeedUpdate(false); - } catch (error) { - console.log(error); - } - }; - if (blockedUsersNeedUpdate && userId) { - loadNewBlockedUsers(); - } - }, [ - setBlockedUsersNeedUpdate, - blockedUsersNeedUpdate, - userId, - setBlockedUsers, - ]); - - useEffect(() => { - const loadTaggUsers = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - await getAllTaggUsers(token, setTaggUsers); - } catch (error) { - console.log(error); - } - }; - loadTaggUsers(); - }, [userId]); - - return ( - { - setUser({...user, userId: id, username}); - }, - logout: () => { - try { - new Promise(() => { - AsyncStorage.removeItem('token'); - AsyncStorage.removeItem('userId'); - AsyncStorage.removeItem('username'); - }).then(() => { - setUser(NO_USER); - }); - } catch (err) { - console.log(err); - } - }, - recentSearches, - taggUsers, - updateMoments: (value) => { - setNewMomentsAvailable(value); - }, - socialsNeedUpdate: (socials: string[]) => { - setSocialsNeedUpdate(socials); - }, - updateFollowers: (value) => { - setFollowersNeedUpdate(value); - }, - updateBlockedUsers: (value) => { - setBlockedUsersNeedUpdate(value); - }, - updateIsEditedProfile: (value: boolean) => { - setIsEditedProfile(value); - }, - }}> - {children} - - ); -}; - -export default AuthProvider; diff --git a/src/routes/authentication/index.ts b/src/routes/authentication/index.ts deleted file mode 100644 index 9968ae93..00000000 --- a/src/routes/authentication/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './AuthProvider'; -export {default} from './AuthProvider'; diff --git a/src/routes/index.ts b/src/routes/index.ts index 7e8a84ce..3b74e130 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,7 +1,3 @@ -export {default as AuthProvider} from './authentication'; -export {default as ProfileProvider} from './viewProfile'; -export * from './authentication'; -export * from './viewProfile'; export * from './onboarding'; export * from './profile'; export {default} from './Routes'; diff --git a/src/routes/profile/Profile.tsx b/src/routes/profile/Profile.tsx index b6672c85..f47d25c4 100644 --- a/src/routes/profile/Profile.tsx +++ b/src/routes/profile/Profile.tsx @@ -11,17 +11,17 @@ import { } from '../../screens'; import {ProfileStack, ProfileStackParams} from './ProfileStack'; import {RouteProp} from '@react-navigation/native'; +import {ScreenType} from '../../types'; /** - * What will be the First Screen of the stack depends on value of isProfileView (Search if its true else Profile) * Trying to explain the purpose of each route on the stack (ACTUALLY A STACK) - * Profile : To display the logged in user's profile when isProfileView is false, else displays profile of any user the logged in user wants to view. - * When you click on the profile icon after looking at a user's profile, the stack is reset and you come back to the top of the stack (First screen : Profile in this case) - * Search : To display the search screen. Search for a user on this screen, click on a result tile and navigate to the same (isProfileView = true). + * Profile : To display the logged in user's profile when the userXId passed in to it is (undefined | null | empty string) else displays profile of the user being visited. + * Search : To display the search screen. Search for a user on this screen, click on a result tile and navigate to the same. * When you click on the search icon after looking at a user's profile, the stack gets reset and you come back to the top of the stack (First screen : Search in this case) * SocialMediaTaggs : To display user data for any social media account set up by the user. * IndividualMoment : To display individual images uploaded by the user (Navigate to comments from this screen, click on a commenter's profile pic / username, look at a user's profile. Click on the profile icon again to come back to your own profile). * MomentCommentsScreen : Displays comments posted by users on an image uploaded by the user. + * EditProfile : To edit logged in user's information. */ type ProfileStackRouteProps = RouteProp; @@ -31,7 +31,12 @@ interface ProfileStackProps { } const Profile: React.FC = ({route}) => { - const {isProfileView} = route.params; + const {screenType} = route.params; + + /** + * This parameter isProfileStack acts as a switch between Search and Profile Stacks + */ + const isProfileStack = screenType === ScreenType.Profile; return ( = ({route}) => { }), }} mode="modal" - initialRouteName={!isProfileView ? 'Profile' : 'Search'}> + initialRouteName={isProfileStack ? 'Profile' : 'Search'}> - {isProfileView ? ( - + {!isProfileStack ? ( + ) : ( )} @@ -77,8 +88,9 @@ const Profile: React.FC = ({route}) => { headerBackTitleVisible: false, headerTintColor: 'white', }} + initialParams={{screenType}} /> - {!isProfileView ? ( + {isProfileStack ? ( ) : ( @@ -87,18 +99,18 @@ const Profile: React.FC = ({route}) => { name="IndividualMoment" component={IndividualMoment} options={{headerShown: false}} - initialParams={{isProfileView: isProfileView}} + initialParams={{screenType}} /> { ); diff --git a/src/routes/viewProfile/ProfileProvider.tsx b/src/routes/viewProfile/ProfileProvider.tsx deleted file mode 100644 index f2d27a84..00000000 --- a/src/routes/viewProfile/ProfileProvider.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import AsyncStorage from '@react-native-community/async-storage'; -import React, {createContext, useEffect, useState} from 'react'; -import {Value} from 'react-native-reanimated'; -import {INTEGRATED_SOCIAL_LIST} from '../../constants'; -import { - loadAvatar, - loadCover, - loadProfileInfo, - loadSocialPosts, - loadMoments, - loadFollowers, - loadFollowing, -} from '../../services'; -import { - ProfileType, - SocialAccountType, - ProfilePreviewType, - UserType, - MomentType, -} from '../../types'; - -interface ProfileContextProps { - user: UserType; - profile: ProfileType; - avatar: string | null; - cover: string | null; - socialAccounts: Record; - socialsNeedUpdate: (_: string[]) => void; - moments: MomentType[]; - followers: ProfilePreviewType[]; - following: ProfilePreviewType[]; - followersNeedUpdate: boolean; - updateFollowers: (value: boolean) => void; -} -const NO_USER: UserType = { - userId: '', - username: '', -}; -const NO_PROFILE: ProfileType = { - biography: '', - website: '', - name: '', -}; - -const NO_SOCIAL_ACCOUNTS: Record = { - Instagram: {posts: []}, - Facebook: {posts: []}, - Twitter: {posts: []}, -}; - -export const ProfileContext = createContext({ - user: NO_USER, - profile: NO_PROFILE, - avatar: null, - cover: null, - socialAccounts: NO_SOCIAL_ACCOUNTS, - socialsNeedUpdate: () => {}, - moments: [], - followers: [], - following: [], - followersNeedUpdate: true, - updateFollowers: () => {}, -}); - -/** - * This is the context provider for user profiles that the logged in user wants to see - * The ProfileProviderProps is used to initialise data as soon as the component is initialised. - */ - -type ProfileProviderProps = { - uname: string; - uId: string; -}; - -const ProfileProvider: React.FC = ({ - children, - uId, - uname, -}) => { - const [user, setUser] = useState({userId: uId, username: uname}); - const [profile, setProfile] = useState(NO_PROFILE); - const [avatar, setAvatar] = useState(null); - const [cover, setCover] = useState(null); - const [newMomentsAvailable, setNewMomentsAvailable] = useState(true); - const [socialAccounts, setSocialAccounts] = useState< - Record - >(NO_SOCIAL_ACCOUNTS); - const [moments, setMoments] = useState>([]); - const [followers, setFollowers] = useState>([]); - const [following, setFollowing] = useState>([]); - const [followersNeedUpdate, setFollowersNeedUpdate] = useState(true); - // Default update all integrated social lists on start - const [socialsNeedUpdate, setSocialsNeedUpdate] = useState([ - ...INTEGRATED_SOCIAL_LIST, - ]); - - const {userId} = user; - useEffect(() => { - if (!userId) { - return; - } - const loadData = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - loadProfileInfo(token, userId, setProfile); - loadAvatar(token, userId, setAvatar); - loadCover(token, userId, setCover); - } catch (err) { - console.log(err); - } - }; - loadData(); - }, [userId]); - - useEffect(() => { - const loadNewMoments = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - const newMoments = await loadMoments(userId, token); - if (newMoments) { - setMoments(newMoments); - } - setNewMomentsAvailable(false); - } catch (error) { - console.log(error); - } - }; - if (newMomentsAvailable && userId) { - loadNewMoments(); - } - }, [newMomentsAvailable, userId]); - - useEffect(() => { - const loadNewFollowers = async () => { - try { - const token = await AsyncStorage.getItem('token'); - if (!token) { - setUser(NO_USER); - return; - } - loadFollowers(userId, token, setFollowers); - loadFollowing(userId, token, setFollowing); - setFollowersNeedUpdate(false); - } catch (error) { - console.log(error); - } - }; - if (followersNeedUpdate && userId) { - loadNewFollowers(); - } - }, [followersNeedUpdate, userId, followers, following]); - - useEffect(() => { - if (socialsNeedUpdate.length > 0 && userId) { - for (let social of socialsNeedUpdate) { - loadSocialPosts(userId, social).then((accountData) => { - /** - * Please use the following syntax when updating an object, fixing this problem broke our head. LOLs - * ref1 : https://stackoverflow.com/questions/56423256/set-dynamic-key-in-state-via-usestate-react-hooks - * ref2: https://stackoverflow.com/questions/43638938/updating-an-object-with-setstate-in-react/43639228 - * The spread operator {...} helps us make a simple copy of the object - * And form there on we can use the [] to specify the dynamically constructed key and set its value. - */ - setSocialAccounts((prevSocialAccounts) => ({ - ...prevSocialAccounts, - [social]: accountData, - })); - console.log('Updated posts data', social); - }); - } - setSocialsNeedUpdate([]); - } - }, [socialAccounts, socialsNeedUpdate, userId]); - - return ( - { - setSocialsNeedUpdate(socials); - }, - updateFollowers: (value) => { - setFollowersNeedUpdate(value); - }, - }}> - {children} - - ); -}; - -export default ProfileProvider; diff --git a/src/routes/viewProfile/index.ts b/src/routes/viewProfile/index.ts deleted file mode 100644 index 7035ce4a..00000000 --- a/src/routes/viewProfile/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ProfileProvider'; -export {default} from './ProfileProvider'; diff --git a/src/screens/onboarding/Checkpoint.tsx b/src/screens/onboarding/Checkpoint.tsx index 0be1e831..83a8a2bc 100644 --- a/src/screens/onboarding/Checkpoint.tsx +++ b/src/screens/onboarding/Checkpoint.tsx @@ -11,7 +11,6 @@ import { } from 'react-native'; import {OnboardingStackParams} from '../../routes'; -import {AuthContext} from '../../routes/authentication'; import {RegistrationWizard, Background} from '../../components'; type CheckpointRouteProp = RouteProp; diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 8f19ec3d..2ddae403 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -14,11 +14,12 @@ import { } from 'react-native'; import {OnboardingStackParams} from '../../routes/onboarding'; -import {AuthContext} from '../../routes/authentication'; import {Background, TaggInput, SubmitButton} from '../../components'; import {usernameRegex, LOGIN_ENDPOINT} from '../../constants'; import AsyncStorage from '@react-native-community/async-storage'; import {UserType} from '../../types'; +import {useDispatch} from 'react-redux'; +import {userLogin} from '../../utils'; type VerificationScreenRouteProp = RouteProp; type VerificationScreenNavigationProp = StackNavigationProp< @@ -51,9 +52,15 @@ const Login: React.FC = ({navigation}: LoginProps) => { attemptedSubmit: false, token: '', }); - // determines if user is logged in - const {login} = React.useContext(AuthContext); const [user, setUser] = useState(NO_USER); + + /** + * Redux Store stuff + * Get the dispatch reference + */ + + const dispatch = useDispatch(); + /** * Updates the state of username. Also verifies the input of the username field by ensuring proper length and appropriate characters. */ @@ -140,7 +147,7 @@ const Login: React.FC = ({navigation}: LoginProps) => { await AsyncStorage.setItem('token', data.token); await AsyncStorage.setItem('userId', data.UserID); await AsyncStorage.setItem('username', username); - login(data.UserID, username); + userLogin(dispatch, {userId: data.UserID, username}); } catch (err) { setUser(NO_USER); console.log(data); diff --git a/src/screens/onboarding/SocialMedia.tsx b/src/screens/onboarding/SocialMedia.tsx index 57270e4e..ee2bed10 100644 --- a/src/screens/onboarding/SocialMedia.tsx +++ b/src/screens/onboarding/SocialMedia.tsx @@ -10,6 +10,7 @@ import { TouchableOpacity, View, } from 'react-native'; +import {useDispatch} from 'react-redux'; import {LinkerType} from 'src/types'; import { Background, @@ -17,7 +18,8 @@ import { RegistrationWizard, } from '../../components'; import {SOCIAL_LIST} from '../../constants/'; -import {AuthContext, OnboardingStackParams} from '../../routes'; +import {OnboardingStackParams} from '../../routes'; +import {userLogin} from '../../utils'; /** * Social Media Screen for displaying social media linkers @@ -33,12 +35,6 @@ const SocialMedia: React.FC = ({route}) => { const {userId, username} = route.params; const linkers: Array = []; - /** - * login: determines if user successully created an account to - * navigate to home and display main tab navigation bar - */ - const {login} = React.useContext(AuthContext); - // let numSocials: Number = state.showMore ? 9 : 3; for (let i = 0; i < SOCIAL_LIST.length; i++) { @@ -48,6 +44,8 @@ const SocialMedia: React.FC = ({route}) => { linkers.push(linker); } + const dispatch = useDispatch(); + /** * Just commenting this out, in case we need it in the future */ @@ -60,7 +58,7 @@ const SocialMedia: React.FC = ({route}) => { const handleLogin = () => { try { - login(userId, username); + userLogin(dispatch, {userId: userId, username: username}); } catch (error) { console.log(error); Alert.alert('There was a problem logging you in'); diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 08dd8e5b..e9eed668 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -18,7 +18,9 @@ import {ProfileStackParams} from 'src/routes'; import {StackNavigationProp} from '@react-navigation/stack'; import {CaptionScreenHeader} from '../../components/'; import {MOMENTS_ENDPOINT} from '../../constants'; -import {AuthContext} from '../../routes/authentication'; +import {useDispatch, useSelector} from 'react-redux'; +import {loadUserMoments} from '../../store/actions'; +import {RootState} from '../../store/rootReducer'; /** * Upload Screen to allow users to upload posts to Tagg @@ -34,11 +36,11 @@ interface CaptionScreenProps { } const CaptionScreen: React.FC = ({route, navigation}) => { - const {title, image} = route.params; + const {title, image, screenType} = route.params; const { user: {userId}, - updateMoments, - } = React.useContext(AuthContext); + } = useSelector((state: RootState) => state.user); + const dispatch = useDispatch(); const [caption, setCaption] = React.useState(''); const handleCaptionUpdate = (caption: string) => { @@ -54,6 +56,14 @@ const CaptionScreen: React.FC = ({route, navigation}) => { return true; }; + const navigateToProfile = () => { + //Since the logged In User is navigating to own profile, useXId is not required + navigation.navigate('Profile', { + screenType, + userXId: undefined, + }); + }; + const handleShare = async () => { try { const request = new FormData(); @@ -86,8 +96,8 @@ const CaptionScreen: React.FC = ({route, navigation}) => { let data = await response.json(); if (statusCode === 200 && checkImageUploadStatus(data)) { Alert.alert('The picture was uploaded successfully!'); - updateMoments(true); - navigation.navigate('Profile', {isProfileView: false}); + dispatch(loadUserMoments(userId)); + navigateToProfile(); } else { Alert.alert('An error occured while uploading. Please try again!'); } @@ -107,9 +117,7 @@ const CaptionScreen: React.FC = ({route, navigation}) => {