diff options
author | Ashm Walia <40498934+ashmgarv@users.noreply.github.com> | 2020-10-24 16:12:39 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-24 19:12:39 -0400 |
commit | 84d283b44f2b6cecb757edcd94e717a36c3ba3c3 (patch) | |
tree | 532a6c415c57b90bb90243b2d99845bb3e93d058 /src | |
parent | 8b680e97ad4689493d2c398281cc0da8e333aa04 (diff) |
[TMA 301] Add follow/unfollow button to profile (#70)
* Follow Unfollow User
* Fixed an issue and moved api call to Content.tsx
* last
* Small changes
Diffstat (limited to 'src')
-rw-r--r-- | src/components/profile/Content.tsx | 83 | ||||
-rw-r--r-- | src/components/profile/FollowUnfollow.tsx | 40 | ||||
-rw-r--r-- | src/components/profile/ProfileBody.tsx | 22 | ||||
-rw-r--r-- | src/constants/api.ts | 3 | ||||
-rw-r--r-- | src/screens/profile/CaptionScreen.tsx | 2 | ||||
-rw-r--r-- | src/services/UserFollowServices.ts | 68 | ||||
-rw-r--r-- | src/services/UserProfileService.ts | 2 | ||||
-rw-r--r-- | src/services/index.ts | 1 |
8 files changed, 215 insertions, 6 deletions
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 8f20cd8d..8a3c36ff 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -3,7 +3,7 @@ import React, {useCallback, useEffect, useState} from 'react'; import {Alert, LayoutChangeEvent, StyleSheet, View} from 'react-native'; import Animated from 'react-native-reanimated'; import {AuthContext, ProfileContext} from '../../routes/'; -import {MomentType} from 'src/types'; +import {MomentType, ProfilePreviewType} from 'src/types'; import {defaultMoments, MOMENTS_ENDPOINT} from '../../constants'; import {SCREEN_HEIGHT} from '../../utils'; import TaggsBar from '../taggs/TaggsBar'; @@ -11,6 +11,7 @@ import {Moment} from '../moments'; import ProfileBody from './ProfileBody'; import ProfileCutout from './ProfileCutout'; import ProfileHeader from './ProfileHeader'; +import {loadFollowers, followOrUnfollowUser} from '../../services'; interface ContentProps { y: Animated.Value<number>; @@ -22,10 +23,23 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { const {newMomentsAvailable, updateMoments, user} = isProfileView ? React.useContext(ProfileContext) : React.useContext(AuthContext); + const {logout} = React.useContext(AuthContext); const [imagesList, setImagesList] = useState<MomentType[]>([]); const [imagesMap, setImagesMap] = useState<Map<string, MomentType[]>>( new Map(), ); + + const [followed, setFollowed] = React.useState<boolean>(false); + const [followers, setFollowers] = React.useState<Array<ProfilePreviewType>>( + [], + ); + const {user: loggedInUser} = React.useContext(AuthContext); + + /** + * If own profile is being viewed then do not show the follow button. + */ + const isOwnProfile = loggedInUser.username === user.username; + const onLayout = (e: LayoutChangeEvent) => { const {height} = e.nativeEvent.layout; setProfileBodyHeight(height); @@ -80,6 +94,63 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { } }, [userId, createImagesMap, updateMoments, newMomentsAvailable]); + /** + * This hook is called just on the load of profile. + */ + useEffect(() => { + const updateFollowedValue = async () => { + const token = await AsyncStorage.getItem('token'); + if (!token) { + logout(); + return; + } + + const listFollowers: ProfilePreviewType[] = await loadFollowers( + userId, + token, + ); + + /** + * Check if the logged in user actually follows the user being viewed. + */ + const isActuallyFollowed = listFollowers.some( + (follower) => follower.username === loggedInUser.username, + ); + + if (followed != isActuallyFollowed) { + setFollowed(isActuallyFollowed); + } + setFollowers(listFollowers); + }; + + /** + * Update the followed value if and only if a profile is being viewed and you are loading a profile afresh that is not your own profile. + */ + if (isProfileView && !isOwnProfile) { + updateFollowedValue(); + } + }, []); + + /** + * Handles a click on the follow / unfollow button. + */ + const handleFollowUnfollow = async () => { + const token = await AsyncStorage.getItem('token'); + if (!token) { + logout(); + return; + } + const isUpdatedSuccessful = await followOrUnfollowUser( + loggedInUser.userId, + userId, + token, + followed, + ); + if (isUpdatedSuccessful) { + setFollowed(!followed); + } + }; + return ( <Animated.ScrollView style={styles.container} @@ -90,7 +161,15 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { <ProfileCutout> <ProfileHeader {...{isProfileView}} /> </ProfileCutout> - <ProfileBody {...{onLayout, isProfileView}} /> + <ProfileBody + {...{ + onLayout, + isProfileView, + isOwnProfile, + followed, + handleFollowUnfollow, + }} + /> <TaggsBar {...{y, profileBodyHeight, isProfileView}} /> <View style={styles.momentsContainer}> {defaultMoments.map((title, index) => ( diff --git a/src/components/profile/FollowUnfollow.tsx b/src/components/profile/FollowUnfollow.tsx new file mode 100644 index 00000000..bb1e9ef7 --- /dev/null +++ b/src/components/profile/FollowUnfollow.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import {StyleSheet, Text} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; + +type FollowUnfollowProps = { + followed: boolean; + handleFollowUnfollow: Function; +}; + +const FollowUnfollow: React.FC<FollowUnfollowProps> = ({ + followed, + handleFollowUnfollow, +}) => { + return ( + <TouchableOpacity + style={styles.button} + onPress={() => handleFollowUnfollow()}> + <Text style={styles.text}>{!followed ? `Follow` : `Unfollow`}</Text> + </TouchableOpacity> + ); +}; + +const styles = StyleSheet.create({ + button: { + justifyContent: 'center', + alignItems: 'center', + width: 110, + height: 40, + borderRadius: 8, + marginTop: '5%', + borderColor: '#698dd3', + backgroundColor: 'white', + borderWidth: 3, + }, + text: { + fontWeight: 'bold', + color: '#698dd3', + }, +}); +export default FollowUnfollow; diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index 53b86708..7091a077 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,24 +1,44 @@ import React from 'react'; import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; import {AuthContext, ProfileContext} from '../../routes/'; +import FollowUnfollow from './FollowUnfollow'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; isProfileView: boolean; + followed: boolean; + isOwnProfile: boolean; + handleFollowUnfollow: Function; } -const ProfileBody: React.FC<ProfileBodyProps> = ({onLayout, isProfileView}) => { +const ProfileBody: React.FC<ProfileBodyProps> = ({ + onLayout, + isProfileView, + followed, + isOwnProfile, + handleFollowUnfollow, +}) => { const { profile, user: {username}, } = isProfileView ? React.useContext(ProfileContext) : React.useContext(AuthContext); + const {biography, website} = profile; + return ( <View onLayout={onLayout} style={styles.container}> <Text style={styles.username}>{`@${username}`}</Text> <Text style={styles.biography}>{`${biography}`}</Text> <Text style={styles.website}>{`${website}`}</Text> + {isProfileView && !isOwnProfile ? ( + <FollowUnfollow + followed={followed} + handleFollowUnfollow={handleFollowUnfollow} + /> + ) : ( + <></> + )} </View> ); }; diff --git a/src/constants/api.ts b/src/constants/api.ts index 181247dd..d9e199d2 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -17,6 +17,9 @@ export const SEARCH_ENDPOINT: string = API_URL + 'search/'; export const MOMENTS_ENDPOINT: string = API_URL + 'moments/'; export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/'; export const COMMENTS_ENDPOINT: string = API_URL + 'comments/'; +export const FOLLOW_USER_ENDPOINT: string = API_URL + 'follow/'; +export const UNFOLLOW_USER_ENDPOINT: string = API_URL + 'unfollow/'; +export const FOLLOWERS_ENDPOINT: string = API_URL + 'followers/'; // Social Link export const LINK_IG_ENDPOINT: string = API_URL + 'link-ig/'; diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 9417d1be..9a5cfb93 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -8,7 +8,7 @@ import {UserType} from '../../types'; import {RouteProp} from '@react-navigation/native'; import {ProfileStackParams} from 'src/routes'; import {StackNavigationProp} from '@react-navigation/stack'; -import {CaptionScreenHeader} from '../../components/profile'; +import {CaptionScreenHeader} from '../../components/'; import {MOMENTS_ENDPOINT} from '../../constants'; import {AuthContext} from '../../routes/authentication'; diff --git a/src/services/UserFollowServices.ts b/src/services/UserFollowServices.ts new file mode 100644 index 00000000..bfd8058a --- /dev/null +++ b/src/services/UserFollowServices.ts @@ -0,0 +1,68 @@ +//Abstracted common user follow api calls out here + +import {Alert} from 'react-native'; +import { + FOLLOWERS_ENDPOINT, + FOLLOW_USER_ENDPOINT, + UNFOLLOW_USER_ENDPOINT, +} from '../constants'; + +import {ProfilePreviewType} from 'src/types'; + +export const loadFollowers = async (userId: string, token: string) => { + try { + const response = await fetch(FOLLOWERS_ENDPOINT + `?user_id=${userId}`, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + if (response.status === 200) { + const body = await response.json(); + return body; + } else { + throw new Error(await response.json()); + } + } catch (error) { + console.log(error); + } + return []; +}; + +export const followOrUnfollowUser = async ( + follower: string, + followed: string, + token: string, + isFollowed: boolean, +) => { + try { + const endpoint = isFollowed ? UNFOLLOW_USER_ENDPOINT : FOLLOW_USER_ENDPOINT; + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + follower, + followed, + }), + }); + if (Math.floor(response.status / 100) === 2) { + return true; + } else { + console.log(await response.json()); + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + return false; + } + } catch (error) { + console.log(error); + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + return false; + } +}; diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index 59f9649a..f5523be4 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -5,8 +5,6 @@ import { PROFILE_INFO_ENDPOINT, AVATAR_PHOTO_ENDPOINT, COVER_PHOTO_ENDPOINT, - GET_IG_POSTS_ENDPOINT, - GET_TWITTER_POSTS_ENDPOINT, } from '../constants'; import AsyncStorage from '@react-native-community/async-storage'; diff --git a/src/services/index.ts b/src/services/index.ts index 2abcef95..aa13dbe3 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,2 +1,3 @@ export * from './UserProfileService'; export * from './MomentServices'; +export * from './UserFollowServices'; |