aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAshm Walia <40498934+ashmgarv@users.noreply.github.com>2020-10-24 16:12:39 -0700
committerGitHub <noreply@github.com>2020-10-24 19:12:39 -0400
commit84d283b44f2b6cecb757edcd94e717a36c3ba3c3 (patch)
tree532a6c415c57b90bb90243b2d99845bb3e93d058 /src
parent8b680e97ad4689493d2c398281cc0da8e333aa04 (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.tsx83
-rw-r--r--src/components/profile/FollowUnfollow.tsx40
-rw-r--r--src/components/profile/ProfileBody.tsx22
-rw-r--r--src/constants/api.ts3
-rw-r--r--src/screens/profile/CaptionScreen.tsx2
-rw-r--r--src/services/UserFollowServices.ts68
-rw-r--r--src/services/UserProfileService.ts2
-rw-r--r--src/services/index.ts1
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';