diff options
-rw-r--r-- | src/components/profile/Content.tsx | 57 | ||||
-rw-r--r-- | src/components/profile/FollowUnfollow.tsx | 40 | ||||
-rw-r--r-- | src/components/profile/ProfileBody.tsx | 33 | ||||
-rw-r--r-- | src/components/profile/ProfilePreview.tsx | 21 | ||||
-rw-r--r-- | src/components/profile/ToggleButton.tsx | 44 | ||||
-rw-r--r-- | src/constants/api.ts | 1 | ||||
-rw-r--r-- | src/constants/constants.ts | 5 | ||||
-rw-r--r-- | src/routes/authentication/AuthProvider.tsx | 42 | ||||
-rw-r--r-- | src/services/BlockUserService.ts | 98 | ||||
-rw-r--r-- | src/services/index.ts | 1 | ||||
-rw-r--r-- | src/utils/common.ts | 15 | ||||
-rw-r--r-- | src/utils/index.ts | 1 |
12 files changed, 303 insertions, 55 deletions
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 7afc3fbc..13db60a5 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -11,7 +11,7 @@ import {Moment} from '../moments'; import ProfileBody from './ProfileBody'; import ProfileCutout from './ProfileCutout'; import ProfileHeader from './ProfileHeader'; -import {followOrUnfollowUser} from '../../services'; +import {followOrUnfollowUser, blockOrUnblockUser} from '../../services'; interface ContentProps { y: Animated.Value<number>; @@ -24,10 +24,12 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { ? React.useContext(ProfileContext) : React.useContext(AuthContext); - const {logout} = React.useContext(AuthContext); const { + logout, user: loggedInUser, updateFollowers: updateLoggedInUserFollowers, + blockedUsers, + updateBlockedUsers, } = React.useContext(AuthContext); /** @@ -36,7 +38,8 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { const [imagesMap, setImagesMap] = useState<Map<string, MomentType[]>>( new Map(), ); - const [followed, setFollowed] = React.useState<boolean>(false); + const [isFollowed, setIsFollowed] = React.useState<boolean>(false); + const [isBlocked, setIsBlocked] = React.useState<boolean>(false); /** * If own profile is being viewed then do not show the follow button. @@ -81,11 +84,24 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { const isActuallyFollowed = followers.some( (follower) => follower.username === loggedInUser.username, ); - if (followed != isActuallyFollowed) { - setFollowed(isActuallyFollowed); + if (isFollowed != isActuallyFollowed) { + setIsFollowed(isActuallyFollowed); } }, [followers]); + useEffect(() => { + if (!userId) { + return; + } + + const isActuallyBlocked = blockedUsers.some( + (cur_user) => user.username === cur_user.username, + ); + if (isBlocked != isActuallyBlocked) { + setIsBlocked(isActuallyBlocked); + } + }, [blockedUsers]); + /** * 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. @@ -100,10 +116,33 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { loggedInUser.userId, userId, token, - followed, + isFollowed, + ); + if (isUpdatedSuccessful) { + setIsFollowed(!isFollowed); + updateFollowers(true); + updateLoggedInUserFollowers(true); + } + }; + + /** + * Handles a click on the block / unblock button. + */ + const handleBlockUnblock = async () => { + const token = await AsyncStorage.getItem('token'); + if (!token) { + logout(); + return; + } + const isUpdatedSuccessful = await blockOrUnblockUser( + loggedInUser.userId, + userId, + token, + isBlocked, ); if (isUpdatedSuccessful) { - setFollowed(!followed); + setIsBlocked(!isBlocked); + updateBlockedUsers(true); updateFollowers(true); updateLoggedInUserFollowers(true); } @@ -126,8 +165,10 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { onLayout, isProfileView, isOwnProfile, - followed, + isFollowed, handleFollowUnfollow, + isBlocked, + handleBlockUnblock, }} /> <TaggsBar {...{y, profileBodyHeight, isProfileView}} /> diff --git a/src/components/profile/FollowUnfollow.tsx b/src/components/profile/FollowUnfollow.tsx deleted file mode 100644 index fa224be3..00000000 --- a/src/components/profile/FollowUnfollow.tsx +++ /dev/null @@ -1,40 +0,0 @@ -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 7091a077..db8c6e0b 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,21 +1,26 @@ import React from 'react'; import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; +import {TOGGLE_BUTTON_TYPE} from '../../constants'; import {AuthContext, ProfileContext} from '../../routes/'; -import FollowUnfollow from './FollowUnfollow'; +import ToggleButton from './ToggleButton'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; isProfileView: boolean; - followed: boolean; + isFollowed: boolean; + isBlocked: boolean; isOwnProfile: boolean; handleFollowUnfollow: Function; + handleBlockUnblock: Function; } const ProfileBody: React.FC<ProfileBodyProps> = ({ onLayout, isProfileView, - followed, + isFollowed, + isBlocked, isOwnProfile, handleFollowUnfollow, + handleBlockUnblock, }) => { const { profile, @@ -32,10 +37,20 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ <Text style={styles.biography}>{`${biography}`}</Text> <Text style={styles.website}>{`${website}`}</Text> {isProfileView && !isOwnProfile ? ( - <FollowUnfollow - followed={followed} - handleFollowUnfollow={handleFollowUnfollow} - /> + <View style={styles.toggleButtonContainer}> + {!isBlocked && ( + <ToggleButton + toggleState={isFollowed} + handleToggle={handleFollowUnfollow} + buttonType={TOGGLE_BUTTON_TYPE.FOLLOW_UNFOLLOW} + /> + )} + <ToggleButton + toggleState={isBlocked} + handleToggle={handleBlockUnblock} + buttonType={TOGGLE_BUTTON_TYPE.BLOCK_UNBLOCK} + /> + </View> ) : ( <></> )} @@ -44,6 +59,10 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ }; const styles = StyleSheet.create({ + toggleButtonContainer: { + flexDirection: 'row', + flex: 1, + }, container: { paddingVertical: 5, paddingHorizontal: 20, diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index 9c953e7d..abc29422 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -7,12 +7,15 @@ import { StyleSheet, ViewProps, TouchableOpacity, + Alert, } from 'react-native'; import {useNavigation} from '@react-navigation/native'; import RNFetchBlob from 'rn-fetch-blob'; import AsyncStorage from '@react-native-community/async-storage'; import {AVATAR_PHOTO_ENDPOINT} from '../../constants'; import {UserType} from '../../types'; +import {AuthContext} from '../../routes/'; +import {isUserBlocked} from '../../services'; const NO_USER: UserType = { userId: '', username: '', @@ -38,6 +41,7 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ style, }) => { const navigation = useNavigation(); + const {user: loggedInUser, logout} = React.useContext(AuthContext); const [avatarURI, setAvatarURI] = useState<string | null>(null); const [user, setUser] = useState<UserType>(NO_USER); useEffect(() => { @@ -80,6 +84,16 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ * Cache maintains 10 recently searched users, popping off the oldest one if * needed to make space. */ + + const checkIfUserIsBlocked = async (userId: string) => { + const token = await AsyncStorage.getItem('token'); + if (!token) { + logout(); + return false; + } + return await isUserBlocked(userId, loggedInUser.userId, token); + }; + const addToRecentlyStoredAndNavigateToProfile = async () => { let user: ProfilePreviewType = { id, @@ -87,7 +101,14 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ first_name, last_name, }; + try { + //If the logged in user is blocked by the user being viewed, do not proceed. + const isUserBlocked = await checkIfUserIsBlocked(user.id); + if (isUserBlocked) { + Alert.alert('You cannot view this profile'); + return; + } if (!isComment) { const jsonValue = await AsyncStorage.getItem( '@recently_searched_users', diff --git a/src/components/profile/ToggleButton.tsx b/src/components/profile/ToggleButton.tsx new file mode 100644 index 00000000..ff14cdde --- /dev/null +++ b/src/components/profile/ToggleButton.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import {StyleSheet, Text} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {getToggleButtonText, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +type ToggleButtonProps = { + toggleState: boolean; + handleToggle: Function; + buttonType: string; +}; + +const ToggleButton: React.FC<ToggleButtonProps> = ({ + toggleState, + handleToggle, + buttonType, +}) => { + return ( + <TouchableOpacity style={styles.button} onPress={() => handleToggle()}> + <Text style={styles.text}> + {getToggleButtonText(buttonType, toggleState)} + </Text> + </TouchableOpacity> + ); +}; + +const styles = StyleSheet.create({ + button: { + justifyContent: 'center', + alignItems: 'center', + width: SCREEN_WIDTH * 0.25, + height: SCREEN_WIDTH * 0.1, + borderRadius: 8, + marginTop: '3%', + borderColor: '#698dd3', + backgroundColor: 'white', + borderWidth: 3, + marginHorizontal: '1%', + }, + text: { + fontWeight: 'bold', + color: '#698dd3', + }, +}); +export default ToggleButton; diff --git a/src/constants/api.ts b/src/constants/api.ts index 4effc1d3..f69ef943 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -21,6 +21,7 @@ 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/'; export const FOLLOWING_ENDPOINT: string = API_URL + 'following/'; +export const BLOCK_USER_ENDPOINT: string = API_URL + 'block/'; // Register Social Link (Non-integrated) export const LINK_SNAPCHAT_ENDPOINT: string = API_URL + 'link-sc/'; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 123d0617..dbd79b45 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -69,6 +69,11 @@ export const SOCIAL_FONT_COLORS = { YOUTUBE: YOUTUBE_FONT_COLOR, }; +export const TOGGLE_BUTTON_TYPE = { + FOLLOW_UNFOLLOW: 'Follow', + BLOCK_UNBLOCK: 'Block', +}; + // Profile Moments export const defaultMoments: Array<string> = [ 'Early Life', diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx index 383b4d5b..7da47b71 100644 --- a/src/routes/authentication/AuthProvider.tsx +++ b/src/routes/authentication/AuthProvider.tsx @@ -10,6 +10,7 @@ import { loadProfileInfo, loadRecentlySearchedUsers, loadSocialPosts, + loadBlockedUsers, } from '../../services'; import { MomentType, @@ -36,6 +37,9 @@ interface AuthContextProps { following: ProfilePreviewType[]; followersNeedUpdate: boolean; updateFollowers: (value: boolean) => void; + blockedUsers: ProfilePreviewType[]; + blockedUsersNeedUpdate: boolean; + updateBlockedUsers: (value: boolean) => void; } const NO_USER: UserType = { @@ -72,6 +76,9 @@ export const AuthContext = createContext<AuthContextProps>({ following: [], followersNeedUpdate: true, updateFollowers: () => {}, + blockedUsers: [], + blockedUsersNeedUpdate: true, + updateBlockedUsers: () => {}, }); /** @@ -97,6 +104,12 @@ const AuthProvider: React.FC = ({children}) => { const [followers, setFollowers] = useState<Array<ProfilePreviewType>>([]); const [following, setFollowing] = useState<Array<ProfilePreviewType>>([]); const [followersNeedUpdate, setFollowersNeedUpdate] = useState<boolean>(true); + const [blockedUsers, setBlockedUsers] = useState<Array<ProfilePreviewType>>( + [], + ); + const [blockedUsersNeedUpdate, setBlockedUsersNeedUpdate] = useState<boolean>( + true, + ); const {userId} = user; useEffect(() => { @@ -193,6 +206,30 @@ const AuthProvider: React.FC = ({children}) => { } }, [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, + ]); + return ( <AuthContext.Provider value={{ @@ -206,6 +243,8 @@ const AuthProvider: React.FC = ({children}) => { followers, following, followersNeedUpdate, + blockedUsers, + blockedUsersNeedUpdate, login: (id, username) => { setUser({...user, userId: id, username}); }, @@ -232,6 +271,9 @@ const AuthProvider: React.FC = ({children}) => { updateFollowers: (value) => { setFollowersNeedUpdate(value); }, + updateBlockedUsers: (value) => { + setBlockedUsersNeedUpdate(value); + }, }}> {children} </AuthContext.Provider> diff --git a/src/services/BlockUserService.ts b/src/services/BlockUserService.ts new file mode 100644 index 00000000..56243729 --- /dev/null +++ b/src/services/BlockUserService.ts @@ -0,0 +1,98 @@ +//Abstracted common block user api calls out here + +import {Alert} from 'react-native'; +import {BLOCK_USER_ENDPOINT} from '../constants'; + +export const loadBlockedUsers = async ( + userId: string, + token: string, + callback: Function, +) => { + try { + const response = await fetch(BLOCK_USER_ENDPOINT + `?user_id=${userId}`, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + if (response.status === 200) { + const body = await response.json(); + callback(body); + } else { + throw new Error(await response.json()); + } + } catch (error) { + console.log(error); + } +}; + +export const blockOrUnblockUser = async ( + blocker: string, + blocked: string, + token: string, + isBlocked: boolean, +) => { + try { + const endpoint = BLOCK_USER_ENDPOINT + (isBlocked ? `${blocker}/` : ''); + + const response = await fetch(endpoint, { + method: isBlocked ? 'DELETE' : 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + blocked, + }), + }); + 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; + } +}; + +export const isUserBlocked = async ( + blocker: string, + blocked: string, + token: string, +) => { + try { + const ext = `${blocked}/?blocker=${blocker}`; + + const response = await fetch(BLOCK_USER_ENDPOINT + ext, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + + if (Math.floor(response.status / 100) === 2) { + const data = await response.json(); + return data['is_blocked']; + } 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?", + ); + } + } catch (error) { + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + } +}; diff --git a/src/services/index.ts b/src/services/index.ts index 6d0f4314..3d74290c 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,3 +2,4 @@ export * from './UserProfileService'; export * from './SocialLinkingService'; export * from './MomentServices'; export * from './UserFollowServices'; +export * from './BlockUserService'; diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 00000000..9e74ca33 --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,15 @@ +import {TOGGLE_BUTTON_TYPE} from '../constants'; + +export const getToggleButtonText: ( + button_type: string, + state: boolean, +) => string | null = (button_type, state) => { + switch (button_type) { + case TOGGLE_BUTTON_TYPE.FOLLOW_UNFOLLOW: + return state ? 'Unfollow' : 'Follow'; + case TOGGLE_BUTTON_TYPE.BLOCK_UNBLOCK: + return state ? 'Unblock' : 'Block'; + default: + return null; + } +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index a7e45979..95449c54 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './screenDimensions'; export * from './statusBarHeight'; export * from './moments'; +export * from './common'; |