diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/common/AcceptDeclineButtons.tsx | 78 | ||||
| -rw-r--r-- | src/components/common/index.ts | 1 | ||||
| -rw-r--r-- | src/components/notifications/Notification.tsx | 89 | ||||
| -rw-r--r-- | src/components/profile/Content.tsx | 60 | ||||
| -rw-r--r-- | src/components/profile/ProfileBody.tsx | 143 | ||||
| -rw-r--r-- | src/components/profile/ProfilePreview.tsx | 27 |
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', ); |
