aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/common/AcceptDeclineButtons.tsx78
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/notifications/Notification.tsx89
-rw-r--r--src/components/profile/Content.tsx60
-rw-r--r--src/components/profile/ProfileBody.tsx143
-rw-r--r--src/components/profile/ProfilePreview.tsx27
6 files changed, 302 insertions, 96 deletions
diff --git a/src/components/common/AcceptDeclineButtons.tsx b/src/components/common/AcceptDeclineButtons.tsx
new file mode 100644
index 00000000..221056c0
--- /dev/null
+++ b/src/components/common/AcceptDeclineButtons.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native';
+import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {ProfilePreviewType} from '../../types';
+import {SCREEN_WIDTH} from '../../utils';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+
+interface AcceptDeclineButtonsProps {
+ requester: ProfilePreviewType;
+ onAccept: () => void;
+ onReject: () => void;
+ externalStyles?: Record<string, StyleProp<ViewStyle>>;
+}
+const AcceptDeclineButtons: React.FC<AcceptDeclineButtonsProps> = ({
+ requester,
+ onAccept,
+ onReject,
+ externalStyles,
+}) => {
+ return (
+ <View style={[styles.container, externalStyles?.container]}>
+ <TouchableOpacity
+ style={[styles.genericButtonStyle, styles.acceptButton]}
+ onPress={onAccept}>
+ <Text style={[styles.buttonTitle, styles.acceptButtonTitleColor]}>
+ Accept
+ </Text>
+ </TouchableOpacity>
+
+ <TouchableOpacity
+ style={[styles.genericButtonStyle, styles.rejectButton]}
+ onPress={onReject}>
+ <Text style={[styles.buttonTitle, styles.rejectButtonTitleColor]}>
+ Reject
+ </Text>
+ </TouchableOpacity>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ },
+ genericButtonStyle: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: SCREEN_WIDTH * 0.14,
+ height: SCREEN_WIDTH * 0.06,
+ borderRadius: 5,
+ padding: 0,
+ marginTop: 8,
+ marginRight: '3%',
+ },
+ acceptButton: {
+ padding: 0,
+ backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ },
+ rejectButton: {
+ borderWidth: 1,
+ backgroundColor: 'white',
+ borderColor: TAGG_TEXT_LIGHT_BLUE,
+ },
+ acceptButtonTitleColor: {
+ color: 'white',
+ },
+ rejectButtonTitleColor: {
+ color: TAGG_TEXT_LIGHT_BLUE,
+ },
+ buttonTitle: {
+ padding: 0,
+ fontSize: 14,
+ fontWeight: '800',
+ },
+});
+
+export default AcceptDeclineButtons;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 9162ec70..61c7fa26 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -19,3 +19,4 @@ export {default as TaggLoadingTndicator} from './TaggLoadingIndicator';
export {default as GenericMoreInfoDrawer} from './GenericMoreInfoDrawer';
export {default as TaggPopUp} from './TaggPopup';
export {default as TaggPrompt} from './TaggPrompt';
+export {default as AcceptDeclineButtons} from './AcceptDeclineButtons';
diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx
index 94937053..e6d16f82 100644
--- a/src/components/notifications/Notification.tsx
+++ b/src/components/notifications/Notification.tsx
@@ -1,12 +1,25 @@
import {useNavigation} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
import {Image, StyleSheet, Text, View} from 'react-native';
+import {Button} from 'react-native-elements';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
import {useDispatch, useStore} from 'react-redux';
+import {
+ declineFriendRequest,
+ loadUserNotifications,
+ updateUserXFriends,
+} from '../../store/actions';
+import {acceptFriendRequest} from '../../store/actions';
+import {NotificationType, ProfilePreviewType, ScreenType, MomentType} from '../../types';
+import {
+ fetchUserX,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+ userXInStore,
+} from '../../utils';
+import AcceptDeclineButtons from '../common/AcceptDeclineButtons';
import {loadAvatar, loadMomentThumbnail} from '../../services';
-import {RootState} from '../../store/rootReducer';
-import {MomentType, NotificationType, ScreenType} from '../../types';
-import {fetchUserX, SCREEN_HEIGHT, userXInStore} from '../../utils';
+
interface NotificationProps {
item: NotificationType;
@@ -26,6 +39,7 @@ const Notification: React.FC<NotificationProps> = (props) => {
screenType,
moments: loggedInUserMoments,
} = props;
+
const navigation = useNavigation();
const state: RootState = useStore().getState();
const dispatch = useDispatch();
@@ -33,7 +47,6 @@ const Notification: React.FC<NotificationProps> = (props) => {
const [avatarURI, setAvatarURI] = useState<string | undefined>(undefined);
const [momentURI, setMomentURI] = useState<string | undefined>(undefined);
const backgroundColor = unread ? '#DCF1F1' : 'rgba(0,0,0,0)';
-
useEffect(() => {
let mounted = true;
const loadAvatarImage = async (user_id: string) => {
@@ -66,7 +79,8 @@ const Notification: React.FC<NotificationProps> = (props) => {
const onNotificationTap = async () => {
switch (notification_type) {
- case 'FRD':
+ case 'FRD_ACPT':
+ case 'FRD_REQ':
if (!userXInStore(state, screenType, id)) {
await fetchUserX(
dispatch,
@@ -103,30 +117,52 @@ const Notification: React.FC<NotificationProps> = (props) => {
}
};
+ const handleAcceptRequest = async () => {
+ await dispatch(acceptFriendRequest({id, username, first_name, last_name}));
+ await dispatch(updateUserXFriends(id, state));
+ dispatch(loadUserNotifications());
+ };
+
+ const handleDeclineFriendRequest = async () => {
+ await dispatch(declineFriendRequest(id));
+ dispatch(loadUserNotifications());
+ };
+
return (
- <TouchableWithoutFeedback
- style={[styles.container, {backgroundColor}]}
- onPress={onNotificationTap}>
- <View style={styles.avatarContainer}>
- <Image
- style={styles.avatar}
- source={
- avatarURI
- ? {uri: avatarURI}
- : require('../../assets/images/avatar-placeholder.png')
- }
- />
- </View>
- <View style={styles.contentContainer}>
- <Text style={styles.actorName}>
- {first_name} {last_name}
- </Text>
- <Text>{verbage}</Text>
- </View>
- {notification_type === 'CMT' && notification_object && (
+ <>
+ <TouchableWithoutFeedback
+ style={[styles.container, {backgroundColor}]}
+ onPress={onNotificationTap}>
+ <View style={styles.avatarContainer}>
+ <Image
+ style={styles.avatar}
+ source={
+ avatarURI
+ ? {uri: avatarURI, cache: 'only-if-cached'}
+ : require('../../assets/images/avatar-placeholder.png')
+ }
+ />
+ </View>
+ <View style={styles.contentContainer}>
+ <Text style={styles.actorName}>
+ {first_name} {last_name}
+ </Text>
+ <Text>{verbage}</Text>
+ </View>
+ {notification_type === 'FRD_REQ' && (
+ <View style={styles.buttonsContainer}>
+ <AcceptDeclineButtons
+ requester={{id, username, first_name, last_name}}
+ onAccept={handleAcceptRequest}
+ onReject={handleDeclineFriendRequest}
+ />
+ </View>
+ )}
+ {notification_type === 'CMT' && notification_object && (
<Image style={styles.moment} source={{uri: momentURI}} />
)}
- </TouchableWithoutFeedback>
+ </TouchableWithoutFeedback>
+ </>
);
};
@@ -165,6 +201,7 @@ const styles = StyleSheet.create({
width: 42,
right: '5%',
},
+ buttonsContainer: {},
});
export default Notification;
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 50516b55..e7fb566b 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -12,6 +12,8 @@ import {
import Animated from 'react-native-reanimated';
import {
CategorySelectionScreenType,
+ FriendshipStatusType,
+ MomentCategoryType,
MomentType,
ProfilePreviewType,
ProfileType,
@@ -19,7 +21,13 @@ import {
UserType,
} from '../../types';
import {COVER_HEIGHT, TAGG_TEXT_LIGHT_BLUE} from '../../constants';
-import {fetchUserX, moveCategory, SCREEN_HEIGHT, userLogin} from '../../utils';
+import {
+ fetchUserX,
+ getUserAsProfilePreviewType,
+ moveCategory,
+ SCREEN_HEIGHT,
+ userLogin,
+} from '../../utils';
import TaggsBar from '../taggs/TaggsBar';
import {Moment} from '../moments';
import ProfileBody from './ProfileBody';
@@ -34,6 +42,7 @@ import {
updateUserXFriends,
updateMomentCategories,
deleteUserMomentsForCategory,
+ updateUserXProfileAllScreens,
} from '../../store/actions';
import {
NO_USER,
@@ -199,18 +208,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
]),
);
- /**
- * This hook is called on load of profile and when you update the friends list.
- */
- useEffect(() => {
- const isActuallyAFriend = friendsLoggedInUser.some(
- (friend) => friend.username === user.username,
- );
- if (isFriend != isActuallyAFriend) {
- setIsFriend(isActuallyAFriend);
- }
- }, [friendsLoggedInUser]);
-
useEffect(() => {
const isActuallyBlocked = blockedUsers.some(
(cur_user) => user.username === cur_user.username,
@@ -220,38 +217,29 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
}
}, [blockedUsers, user]);
- /**
- * The object returned by this method is added to the list of blocked / friended users by the reducer.
- * Which helps us prevent an extra api call to the backend just to fetch a user.
+ // Handles click on friend/requested/unfriend button
+ /*
+ * When user logged in clicks on the friend button:
+ A request is sent.
+ Which means you have to update the status of their friendshpi to requested
+ When the status is changed to requested the button should change to requested.
+ When the button is changed to requested and thr user clicks on it,
+ a request much go to the backend to delete that request
+ When that succeeds, their friendship must be updated to no-record again;
+ When the button is changed to no_record, the add friends button should be displayed again
*/
- 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 friend / unfriend button.
- * friendUnfriendUser takes care of updating the friends list for loggedInUser
- * updateUserXFriends updates friends list for the new friend.
- */
-
const handleFriendUnfriend = async () => {
+ const {friendship_status} = profile;
await dispatch(
friendUnfriendUser(
loggedInUser,
getUserAsProfilePreviewType(user, profile),
- isFriend,
+ friendship_status,
+ screenType,
),
);
await dispatch(updateUserXFriends(user.userId, state));
+ dispatch(updateUserXProfileAllScreens(user.userId, state));
};
/**
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index 57b617d8..64aec09c 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -1,15 +1,28 @@
import React from 'react';
import {StyleSheet, View, Text, LayoutChangeEvent, Linking} from 'react-native';
-import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants';
+import {Button} from 'react-native-elements';
+import {
+ TAGG_DARK_BLUE,
+ TAGG_TEXT_LIGHT_BLUE,
+ TOGGLE_BUTTON_TYPE,
+} from '../../constants';
import ToggleButton from './ToggleButton';
import {RootState} from '../../store/rootReducer';
-import {useSelector} from 'react-redux';
-import {ScreenType} from '../../types';
+import {useDispatch, useSelector, useStore} from 'react-redux';
+import {FriendshipStatusType, ScreenType} from '../../types';
import {NO_PROFILE} from '../../store/initialStates';
+import {getUserAsProfilePreviewType, SCREEN_WIDTH} from '../../utils';
+import {AcceptDeclineButtons} from '../common';
+import {
+ acceptFriendRequest,
+ declineFriendRequest,
+ loadUserNotifications,
+ updateUserXFriends,
+ updateUserXProfileAllScreens,
+} from '../../store/actions';
interface ProfileBodyProps {
onLayout: (event: LayoutChangeEvent) => void;
- isFriend: boolean;
isBlocked: boolean;
handleFriendUnfriend: () => void;
handleBlockUnblock: () => void;
@@ -18,21 +31,42 @@ interface ProfileBodyProps {
}
const ProfileBody: React.FC<ProfileBodyProps> = ({
onLayout,
- isFriend,
isBlocked,
handleFriendUnfriend,
handleBlockUnblock,
userXId,
screenType,
}) => {
- const {
- profile = NO_PROFILE,
- user: {username},
- } = userXId
+ const {profile = NO_PROFILE, user} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.user);
- const {biography, website} = profile;
+ const {
+ biography,
+ website,
+ friendship_status,
+ friendship_requester_id,
+ } = profile;
+
+ const {id, username, first_name, last_name} = getUserAsProfilePreviewType(
+ user,
+ profile,
+ );
+
+ const state: RootState = useStore().getState();
+ const dispatch = useDispatch();
+
+ const handleAcceptRequest = async () => {
+ await dispatch(acceptFriendRequest({id, username, first_name, last_name}));
+ await dispatch(updateUserXFriends(id, state));
+ dispatch(updateUserXProfileAllScreens(id, state));
+ };
+
+ const handleDeclineFriendRequest = async () => {
+ await dispatch(declineFriendRequest(id));
+ dispatch(updateUserXProfileAllScreens(id, state));
+ };
+
return (
<View onLayout={onLayout} style={styles.container}>
<Text style={styles.username}>{`@${username}`}</Text>
@@ -57,17 +91,47 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
buttonType={TOGGLE_BUTTON_TYPE.BLOCK_UNBLOCK}
/>
</View>
-
)}
{userXId && !isBlocked && (
- <View style={styles.toggleButtonContainer}>
- <ToggleButton
- toggleState={isFriend}
- handleToggle={handleFriendUnfriend}
- buttonType={TOGGLE_BUTTON_TYPE.FRIEND_UNFRIEND}
- />
+ <View style={styles.buttonsContainer}>
+ {friendship_status === 'no_record' && (
+ <Button
+ title={'Add Friend'}
+ buttonStyle={styles.button}
+ titleStyle={styles.buttonTitle}
+ onPress={handleFriendUnfriend} // requested, requested status
+ />
+ )}
+ {friendship_status === 'friends' && (
+ <Button
+ title={'Unfriend'}
+ buttonStyle={styles.button}
+ titleStyle={styles.buttonTitle}
+ onPress={handleFriendUnfriend} // unfriend, no record status
+ />
+ )}
+ {(friendship_status === 'requested' &&
+ friendship_requester_id !== userXId && (
+ <Button
+ title={'Requested'}
+ buttonStyle={styles.requestedButton}
+ titleStyle={styles.requestedButtonTitle}
+ onPress={handleFriendUnfriend} // delete request, no record status
+ />
+ )) ||
+ (friendship_status === 'requested' &&
+ friendship_requester_id === userXId && (
+ <AcceptDeclineButtons
+ requester={getUserAsProfilePreviewType(
+ {userId: userXId, username},
+ profile,
+ )}
+ onAccept={handleAcceptRequest}
+ onReject={handleDeclineFriendRequest}
+ externalStyles={{container: styles.acceptRejectContainer}}
+ />
+ ))}
</View>
-
)}
</View>
);
@@ -80,6 +144,15 @@ const styles = StyleSheet.create({
paddingTop: '3.5%',
paddingBottom: '2%',
},
+ acceptRejectContainer: {
+ flexDirection: 'row',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ flex: 1,
+ paddingTop: '3.5%',
+ paddingBottom: '2%',
+ },
container: {
paddingVertical: '1%',
paddingHorizontal: 18,
@@ -99,6 +172,40 @@ const styles = StyleSheet.create({
color: TAGG_DARK_BLUE,
marginBottom: '1%',
},
+ requestedButton: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: SCREEN_WIDTH * 0.4,
+ height: SCREEN_WIDTH * 0.09,
+ borderColor: TAGG_TEXT_LIGHT_BLUE,
+ borderWidth: 3,
+ borderRadius: 5,
+ marginRight: '2%',
+ padding: 0,
+ backgroundColor: 'transparent',
+ },
+ requestedButtonTitle: {
+ color: TAGG_TEXT_LIGHT_BLUE,
+ padding: 0,
+ fontSize: 14,
+ fontWeight: '700',
+ },
+ buttonTitle: {
+ color: 'white',
+ padding: 0,
+ fontSize: 14,
+ fontWeight: '700',
+ },
+ button: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: SCREEN_WIDTH * 0.4,
+ height: SCREEN_WIDTH * 0.09,
+ padding: 0,
+ borderRadius: 5,
+ marginRight: '2%',
+ backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ },
});
export default ProfileBody;
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
index 23cb2155..e6311daa 100644
--- a/src/components/profile/ProfilePreview.tsx
+++ b/src/components/profile/ProfilePreview.tsx
@@ -18,7 +18,7 @@ import {isUserBlocked, loadAvatar} from '../../services';
import {useSelector, useDispatch, useStore} from 'react-redux';
import {RootState} from '../../store/rootreducer';
import {logout} from '../../store/actions';
-import {fetchUserX, userXInStore} from '../../utils';
+import {checkIfUserIsBlocked, fetchUserX, userXInStore} from '../../utils';
import {SearchResultsBackground} from '../search';
import NavigationBar from 'src/routes/tabs';
import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings';
@@ -73,15 +73,6 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
* needed to make space.
*/
- const checkIfUserIsBlocked = async (userId: string) => {
- const token = await AsyncStorage.getItem('token');
- if (!token) {
- dispatch(logout());
- return false;
- }
- return await isUserBlocked(userId, loggedInUser.userId, token);
- };
-
const state: RootState = useStore().getState();
const addToRecentlyStoredAndNavigateToProfile = async () => {
@@ -93,13 +84,17 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
};
try {
+ //If the logged in user is blocked by the user being viewed, do not proceed.
+ const isUserBlocked = await checkIfUserIsBlocked(
+ user.id,
+ dispatch,
+ loggedInUser,
+ );
+ if (isUserBlocked) {
+ Alert.alert(ERROR_UNABLE_TO_VIEW_PROFILE);
+ return;
+ }
if (previewType !== 'Comment') {
- //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(ERROR_UNABLE_TO_VIEW_PROFILE);
- return;
- }
const jsonValue = await AsyncStorage.getItem(
'@recently_searched_users',
);