diff options
Diffstat (limited to 'src')
31 files changed, 781 insertions, 264 deletions
diff --git a/src/App.tsx b/src/App.tsx index 18fadf64..ea3617dc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,11 @@ -import React, {useEffect} from 'react'; import {NavigationContainer} from '@react-navigation/native'; -import Routes from './routes'; +import React from 'react'; import {Provider} from 'react-redux'; -import store from './store/configureStore'; -import {fcmService} from './services/FCMService'; import {navigationRef} from './RootNavigation'; +import Routes from './routes'; +import store from './store/configureStore'; const App = () => { - useEffect(() => { - fcmService.setUpPushNotifications(); - // TODO: If permissions are not there, deactivateFcmService - }); - return ( /** * This is the provider from the redux store, it acts as the root provider for our application diff --git a/src/assets/icons/findFriends/find-friend-icon.png b/src/assets/icons/findFriends/find-friend-icon.png Binary files differnew file mode 100644 index 00000000..62abcac0 --- /dev/null +++ b/src/assets/icons/findFriends/find-friend-icon.png diff --git a/src/assets/icons/findFriends/lock-icon.png b/src/assets/icons/findFriends/lock-icon.png Binary files differnew file mode 100644 index 00000000..20ccbe2f --- /dev/null +++ b/src/assets/icons/findFriends/lock-icon.png diff --git a/src/assets/icons/findFriends/phone-cross-icon.png b/src/assets/icons/findFriends/phone-cross-icon.png Binary files differnew file mode 100644 index 00000000..0801e9f1 --- /dev/null +++ b/src/assets/icons/findFriends/phone-cross-icon.png diff --git a/src/components/common/FriendsButton.tsx b/src/components/common/FriendsButton.tsx new file mode 100644 index 00000000..6ef23a96 --- /dev/null +++ b/src/components/common/FriendsButton.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import {StyleSheet} from 'react-native'; +import {Button} from 'react-native-elements'; +import {ScreenType} from '../../types'; +import {TAGG_LIGHT_BLUE} from '../../constants'; +import {handleFriendUnfriend, SCREEN_WIDTH} from '../../utils'; +import {NO_PROFILE, NO_USER} from '../../store/initialStates'; +import {useDispatch, useSelector, useStore} from 'react-redux'; +import {RootState} from '../../store/rootReducer'; + +interface ProfileBodyProps { + userXId: string | undefined; + screenType: ScreenType; +} +const FriendsButton: React.FC<ProfileBodyProps> = ({userXId, screenType}) => { + const dispatch = useDispatch(); + + const {user = NO_USER, profile = NO_PROFILE} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + + const {user: loggedInUser = NO_USER} = useSelector( + (state: RootState) => state.user, + ); + + const state = useStore().getState(); + + const {friendship_status} = profile; + + return ( + <> + {friendship_status === 'no_record' && ( + <Button + title={'Add Friend'} + buttonStyle={styles.button} + titleStyle={styles.buttonTitle} + onPress={() => + handleFriendUnfriend( + screenType, + user, + profile, + dispatch, + state, + loggedInUser, + ) + } // requested, requested status + /> + )} + {friendship_status === 'friends' && ( + <Button + title={'Unfriend'} + buttonStyle={styles.requestedButton} + titleStyle={styles.requestedButtonTitle} + onPress={() => + handleFriendUnfriend( + screenType, + user, + profile, + dispatch, + state, + loggedInUser, + ) + } // unfriend, no record status + /> + )} + </> + ); +}; + +const styles = StyleSheet.create({ + requestedButton: { + justifyContent: 'center', + alignItems: 'center', + width: SCREEN_WIDTH * 0.4, + height: SCREEN_WIDTH * 0.075, + borderColor: TAGG_LIGHT_BLUE, + borderWidth: 2, + borderRadius: 0, + marginRight: '2%', + marginLeft: '1%', + padding: 0, + backgroundColor: 'transparent', + }, + requestedButtonTitle: { + color: TAGG_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.075, + padding: 0, + borderWidth: 2, + borderColor: TAGG_LIGHT_BLUE, + borderRadius: 0, + marginRight: '2%', + marginLeft: '1%', + backgroundColor: TAGG_LIGHT_BLUE, + }, +}); + +export default FriendsButton; diff --git a/src/components/common/TaggSquareButton.tsx b/src/components/common/TaggSquareButton.tsx index c6064b92..78a90554 100644 --- a/src/components/common/TaggSquareButton.tsx +++ b/src/components/common/TaggSquareButton.tsx @@ -8,7 +8,7 @@ import { ViewStyle, } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; -import {BACKGROUND_GRADIENT_MAP} from '../../constants'; +import {BACKGROUND_GRADIENT_MAP, TAGG_PURPLE} from '../../constants'; import {normalize, SCREEN_WIDTH} from '../../utils'; interface TaggSquareButtonProps extends ViewProps { @@ -23,7 +23,7 @@ const TaggSquareButton: React.FC<TaggSquareButtonProps> = (props) => { const buttonStyles = (() => { switch (props.color) { case 'purple': - return {backgroundColor: '#8F01FF'}; + return {backgroundColor: TAGG_PURPLE}; case 'white': default: return {backgroundColor: 'white'}; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index a5718c1e..95854ba8 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -20,4 +20,5 @@ export {default as GenericMoreInfoDrawer} from './GenericMoreInfoDrawer'; export {default as TaggPopUp} from './TaggPopup'; export {default as TaggPrompt} from './TaggPrompt'; export {default as AcceptDeclineButtons} from './AcceptDeclineButtons'; +export {default as FriendsButton} from './FriendsButton'; export {default as TaggSquareButton} from './TaggSquareButton'; diff --git a/src/components/moments/IndividualMomentTitleBar.tsx b/src/components/moments/IndividualMomentTitleBar.tsx index bd5b307f..6cdfe0e8 100644 --- a/src/components/moments/IndividualMomentTitleBar.tsx +++ b/src/components/moments/IndividualMomentTitleBar.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {TouchableOpacity} from 'react-native'; import {Text, View, StyleSheet, ViewProps} from 'react-native'; +import {normalize} from '../../utils'; import CloseIcon from '../../assets/ionicons/close-outline.svg'; interface IndividualMomentTitleBarProps extends ViewProps { @@ -30,9 +31,11 @@ const styles = StyleSheet.create({ height: '5%', }, header: { - fontSize: 20, - fontWeight: 'bold', color: 'white', + fontSize: normalize(18), + fontWeight: '700', + lineHeight: normalize(21.48), + letterSpacing: normalize(1.3), }, closeButton: { position: 'absolute', diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx index 951a5bf6..c2c6c4e4 100644 --- a/src/components/notifications/Notification.tsx +++ b/src/components/notifications/Notification.tsx @@ -6,11 +6,7 @@ import LinearGradient from 'react-native-linear-gradient'; import {useDispatch, useStore} from 'react-redux'; import {BACKGROUND_GRADIENT_MAP} from '../../constants'; import {ERROR_DELETED_OBJECT} from '../../constants/strings'; -import { - loadImageFromURL, - loadMoments, - loadMomentThumbnail, -} from '../../services'; +import {loadImageFromURL} from '../../services'; import { acceptFriendRequest, declineFriendRequest, @@ -19,19 +15,22 @@ import { updateUserXFriends, } from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import {MomentType, NotificationType, ScreenType} from '../../types'; import { - fetchUserX, - getTokenOrLogout, - SCREEN_HEIGHT, - userXInStore, -} from '../../utils'; + CommentNotificationType, + CommentThreadType, + MomentType, + NotificationType, + ScreenType, + ThreadNotificationType, + UserType, +} from '../../types'; +import {fetchUserX, SCREEN_HEIGHT, userXInStore} from '../../utils'; import AcceptDeclineButtons from '../common/AcceptDeclineButtons'; interface NotificationProps { item: NotificationType; screenType: ScreenType; - moments: MomentType[]; + loggedInUser: UserType; } const Notification: React.FC<NotificationProps> = (props) => { @@ -44,7 +43,7 @@ const Notification: React.FC<NotificationProps> = (props) => { unread, }, screenType, - moments: loggedInUserMoments, + loggedInUser, } = props; const navigation = useNavigation(); @@ -53,7 +52,6 @@ const Notification: React.FC<NotificationProps> = (props) => { const [avatar, setAvatar] = useState<string | undefined>(undefined); const [momentURI, setMomentURI] = useState<string | undefined>(undefined); - const [onTapLoadProfile, setOnTapLoadProfile] = useState<boolean>(false); useEffect(() => { (async () => { @@ -67,40 +65,22 @@ const Notification: React.FC<NotificationProps> = (props) => { }, []); useEffect(() => { - if (onTapLoadProfile) { - fetchUserX(dispatch, {userId: id, username: username}, screenType); - } - return () => { - setOnTapLoadProfile(false); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [onTapLoadProfile]); - - useEffect(() => { - let mounted = true; - const loadMomentImage = async (moment_id: string) => { - const response = await loadMomentThumbnail(moment_id); - if (mounted && response) { - setMomentURI(response); - } else { - // if not set to empty, it will re-use the previous notification's state - setMomentURI(undefined); - } - }; - if ( - (notification_type === 'CMT' || + if (notification_object) { + let url: string | undefined; + let obj; + if ( notification_type === 'MOM_3+' || - notification_type === 'MOM_FRIEND') && - notification_object - ) { - loadMomentImage( - notification_object.moment_id - ? notification_object.moment_id - : notification_object.parent_comment.moment_id, - ); - return () => { - mounted = false; - }; + notification_type === 'MOM_FRIEND' + ) { + obj = notification_object as MomentType; + url = obj.thumbnail_url; + } else if (notification_type === 'CMT') { + obj = notification_object as CommentNotificationType; + url = obj.notification_data.thumbnail_url; + } + if (url) { + setMomentURI(url); + } } }, [id, notification_object, notification_type]); @@ -126,53 +106,64 @@ const Notification: React.FC<NotificationProps> = (props) => { Alert.alert(ERROR_DELETED_OBJECT); break; } - let {moment_id} = notification_object; - let {comment_id} = notification_object; - //If this is a thread, get comment_id and moment_id from parent_comment - if (!notification_object?.moment_id) { - moment_id = notification_object?.parent_comment?.moment_id; - comment_id = notification_object?.parent_comment?.comment_id; - } + /** + * Notification object knows + * 1 - Which comment + * 2 - Which user + * The comment / reply belongs to + * STEP 1 : Populate reply / comment + * STEP 2 : Load user data if moment does not belong to the logged in user + * STEP 3 : Navigate to relevant moment + */ - // Now find the moment we need to display - let moment: MomentType | undefined = loggedInUserMoments?.find( - (m) => m.moment_id === moment_id, - ); + let comment_id: string; + let not_object; + let reply: CommentThreadType | undefined; let userXId; - // If moment does not belong to the logged in user, then the comment was probably a reply to logged in user's comment - // on userX's moment - // Load moments for userX - if (!moment) { - let moments: MomentType[] = []; - try { - //Populate local state in the mean time - setOnTapLoadProfile(true); - const token = await getTokenOrLogout(dispatch); - moments = await loadMoments(id, token); - } catch (err) { - console.log(err); - } - moment = moments?.find((m) => m.moment_id === moment_id); - userXId = id; + // STEP 1 + if ('parent_comment' in notification_object) { + //This is a reply + not_object = notification_object as ThreadNotificationType; + comment_id = not_object.parent_comment; + reply = { + parent_comment: {comment_id: comment_id}, + comment_id: not_object.comment_id, + }; + } else { + not_object = notification_object as CommentNotificationType; + comment_id = not_object.comment_id; } - //Now if moment was found, navigate to the respective moment + //STEP 2 + const {user, ...moment} = not_object.notification_data; + if (user.id !== loggedInUser.userId) { + fetchUserX( + dispatch, + {userId: user.id, username: user.username}, + screenType, + ); + userXId = user.id; + } + + const {moment_id} = moment; + + //STEP 3 if (moment) { - if (notification_object?.parent_comment) { - dispatch(updateReplyPosted(notification_object)); + if (reply) { + dispatch(updateReplyPosted(reply)); } navigation.push('IndividualMoment', { moment, - userXId: userXId, // we're only viewing our own moment here + userXId, screenType, }); setTimeout(() => { navigation.push('MomentCommentsScreen', { - moment_id: moment_id, + moment_id, screenType, - comment_id: comment_id, + comment_id, }); }, 500); } diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index a35a5820..28000dd7 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -25,11 +25,9 @@ import { import { blockUnblockUser, deleteUserMomentsForCategory, - friendUnfriendUser, loadFriendsData, updateMomentCategories, updateUserXFriends, - updateUserXProfileAllScreens, } from '../../store/actions'; import { EMPTY_MOMENTS_LIST, @@ -216,31 +214,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => { } }, [blockedUsers, 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 handleFriendUnfriend = async () => { - const {friendship_status} = profile; - await dispatch( - friendUnfriendUser( - loggedInUser, - getUserAsProfilePreviewType(user, profile), - friendship_status, - screenType, - ), - ); - await dispatch(updateUserXFriends(user.userId, state)); - dispatch(updateUserXProfileAllScreens(user.userId, state)); - }; - /** * Handles a click on the block / unblock button. * loadFriends updates friends list for the logged in user @@ -332,7 +305,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => { userXId, screenType, isFriend, - handleFriendUnfriend, handleBlockUnblock, isBlocked, }} diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx index 23ce28fe..9b9e5302 100644 --- a/src/components/profile/Friends.tsx +++ b/src/components/profile/Friends.tsx @@ -1,64 +1,123 @@ import React from 'react'; -import {View, StyleSheet, Text} from 'react-native'; +import {View, StyleSheet, ScrollView, Text} from 'react-native'; import {ProfilePreviewType, ScreenType} from '../../types'; import {ProfilePreview} from '..'; -import {useNavigation} from '@react-navigation/native'; import {Button} from 'react-native-elements'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {TAGG_LIGHT_BLUE} from '../../constants'; +import {RootState} from '../../store/rootReducer'; +import {useDispatch, useStore} from 'react-redux'; +import {handleUnfriend} from '../../utils/friends'; +import {NO_USER} from '../../store/initialStates'; +import {TouchableOpacity} from 'react-native-gesture-handler'; interface FriendsProps { result: Array<ProfilePreviewType>; screenType: ScreenType; + userId: string; } -const Friends: React.FC<FriendsProps> = ({result, screenType}) => { - const navigation = useNavigation(); +const Friends: React.FC<FriendsProps> = ({result, screenType, userId}) => { + const state: RootState = useStore().getState(); + const dispatch = useDispatch(); + + const {user: loggedInUser = NO_USER} = state; + return ( <> - <View style={styles.header}> - <Button - title="X" - buttonStyle={styles.button} - titleStyle={styles.buttonText} - onPress={() => { - navigation.pop(); - }} - /> - <Text style={styles.title}>Friends</Text> + <View style={styles.subheader}> + <Text style={styles.subheaderText}>Friends</Text> </View> - {result.map((profilePreview) => ( - <ProfilePreview - style={styles.friend} - key={profilePreview.id} - {...{profilePreview}} - previewType={'Friend'} - screenType={screenType} - /> - ))} + <ScrollView + keyboardShouldPersistTaps={'always'} + stickyHeaderIndices={[4]} + style={styles.scrollView} + contentContainerStyle={styles.scrollViewContent} + showsVerticalScrollIndicator={false}> + {result.map((profilePreview) => ( + <View key={profilePreview.id} style={styles.container}> + <View style={styles.friend}> + <ProfilePreview + {...{profilePreview}} + previewType={'Friend'} + screenType={screenType} + /> + </View> + {loggedInUser.userId === userId && ( + <TouchableOpacity + style={styles.button} + onPress={() => + handleUnfriend(screenType, profilePreview, dispatch, state) + }> + <Text style={styles.buttonTitle}>Unfriend</Text> + </TouchableOpacity> + )} + </View> + ))} + </ScrollView> </> ); }; const styles = StyleSheet.create({ + scrollView: { + flexDirection: 'column', + alignSelf: 'center', + width: SCREEN_WIDTH * 0.85, + height: SCREEN_HEIGHT, + }, + scrollViewContent: { + alignSelf: 'center', + paddingBottom: SCREEN_HEIGHT / 15, + width: SCREEN_WIDTH * 0.85, + height: SCREEN_HEIGHT * 0.75, + marginTop: '1%', + }, header: {flexDirection: 'row'}, - friend: { - marginVertical: 10, + subheader: { + alignSelf: 'center', + width: SCREEN_WIDTH * 0.85, + marginVertical: '3%', + }, + subheaderText: { + color: '#828282', + fontSize: normalize(12), + fontWeight: '600', + lineHeight: normalize(14.32), }, - title: { - position: 'relative', - fontSize: 17, - fontWeight: 'bold', - paddingBottom: 10, - paddingTop: 10, - flexGrow: 1, - paddingLeft: '26%', + container: { + alignSelf: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + height: normalize(42), + alignItems: 'center', + marginBottom: '5%', + }, + friend: { + alignSelf: 'center', + height: '100%', }, button: { + alignSelf: 'center', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '55%', + borderColor: TAGG_LIGHT_BLUE, + borderWidth: 2, + borderRadius: 2, + padding: 0, backgroundColor: 'transparent', }, - buttonText: { - color: 'black', - fontSize: 18, - fontWeight: '400', + buttonTitle: { + color: TAGG_LIGHT_BLUE, + padding: 0, + fontSize: normalize(11), + fontWeight: '700', + lineHeight: normalize(13.13), + letterSpacing: normalize(0.6), + paddingHorizontal: '3.8%', }, }); diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index f2d75519..106b20a7 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -9,14 +9,17 @@ import { import ToggleButton from './ToggleButton'; import {RootState} from '../../store/rootReducer'; 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 {ScreenType} from '../../types'; +import {NO_PROFILE, NO_USER} from '../../store/initialStates'; +import { + getUserAsProfilePreviewType, + handleFriendUnfriend, + SCREEN_WIDTH, +} from '../../utils'; +import {AcceptDeclineButtons, FriendsButton} from '../common'; import { acceptFriendRequest, declineFriendRequest, - loadUserNotifications, updateUserXFriends, updateUserXProfileAllScreens, } from '../../store/actions'; @@ -24,7 +27,6 @@ import { interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; isBlocked: boolean; - handleFriendUnfriend: () => void; handleBlockUnblock: () => void; userXId: string | undefined; screenType: ScreenType; @@ -32,7 +34,6 @@ interface ProfileBodyProps { const ProfileBody: React.FC<ProfileBodyProps> = ({ onLayout, isBlocked, - handleFriendUnfriend, handleBlockUnblock, userXId, screenType, @@ -41,6 +42,10 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ ? useSelector((state: RootState) => state.userX[screenType][userXId]) : useSelector((state: RootState) => state.user); + const {user: loggedInUser = NO_USER} = useSelector( + (state: RootState) => state.user, + ); + const { biography, website, @@ -94,29 +99,23 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ )} {userXId && !isBlocked && ( <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.requestedButton} - titleStyle={styles.requestedButtonTitle} - onPress={handleFriendUnfriend} // unfriend, no record status - /> - )} + <FriendsButton userXId={userXId} screenType={screenType} /> {(friendship_status === 'requested' && friendship_requester_id !== userXId && ( <Button title={'Requested'} buttonStyle={styles.requestedButton} titleStyle={styles.requestedButtonTitle} - onPress={handleFriendUnfriend} // delete request, no record status + onPress={() => + handleFriendUnfriend( + screenType, + user, + profile, + dispatch, + state, + loggedInUser, + ) + } // delete request, no record status /> )) || (friendship_status === 'requested' && diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index 38defb8d..fad3ec09 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -15,7 +15,13 @@ import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings'; import {loadImageFromURL} from '../../services'; import {RootState} from '../../store/rootreducer'; import {PreviewType, ProfilePreviewType, ScreenType} from '../../types'; -import {checkIfUserIsBlocked, fetchUserX, userXInStore} from '../../utils'; +import { + checkIfUserIsBlocked, + fetchUserX, + normalize, + SCREEN_WIDTH, + userXInStore, +} from '../../utils'; /** * This component returns user's profile picture friended by username as a touchable component. @@ -308,29 +314,31 @@ const styles = StyleSheet.create({ friendContainer: { flexDirection: 'row', alignItems: 'center', - marginVertical: 10, + justifyContent: 'flex-start', + width: SCREEN_WIDTH * 0.6, }, friendAvatar: { - height: 42, - width: 42, + height: normalize(42), + width: normalize(42), marginRight: 15, - borderRadius: 20, + borderRadius: 50, }, friendNameContainer: { + height: '100%', justifyContent: 'space-evenly', alignSelf: 'stretch', }, friendUsername: { - fontSize: 14, - fontWeight: '700', - color: '#3C3C3C', - letterSpacing: 0.6, + fontSize: normalize(14), + fontWeight: '500', + color: '#000', + letterSpacing: normalize(0.1), }, friendName: { - fontSize: 12, - fontWeight: '500', + fontSize: normalize(12), + fontWeight: '400', color: '#6C6C6C', - letterSpacing: 0.5, + letterSpacing: normalize(0.1), }, }); diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 7fcc457f..f58aa686 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -60,6 +60,7 @@ export const LINKEDIN_FONT_COLOR: string = '#78B5FD'; export const SNAPCHAT_FONT_COLOR: string = '#FFFC00'; export const YOUTUBE_FONT_COLOR: string = '#FCA4A4'; +export const TAGG_PURPLE = '#8F01FF'; export const TAGG_DARK_BLUE = '#4E699C'; export const TAGG_LIGHT_BLUE: string = '#698DD3'; export const TAGG_LIGHT_PURPLE = '#F4DDFF'; @@ -131,8 +132,8 @@ export const BACKGROUND_GRADIENT_MAP: Record< [BackgroundGradientType.Light]: ['#9F00FF', '#27EAE9'], [BackgroundGradientType.Dark]: ['#421566', '#385D5E'], [BackgroundGradientType.Notification]: [ - 'rgba(143, 1, 255, 0.5)', - 'rgba(110, 231, 231, 0.5)', + 'rgba(143, 1, 255, 0.4)', + 'rgba(110, 231, 231, 0.4)', ], }; diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx index a14f1576..a5383a47 100644 --- a/src/routes/Routes.tsx +++ b/src/routes/Routes.tsx @@ -7,6 +7,7 @@ import {userLogin} from '../utils'; import SplashScreen from 'react-native-splash-screen'; import messaging from '@react-native-firebase/messaging'; import {updateNewNotificationReceived} from '../store/actions'; +import {fcmService} from '../services'; const Routes: React.FC = () => { const { @@ -39,6 +40,13 @@ const Routes: React.FC = () => { } }, [dispatch, userId]); + useEffect(() => { + if (userId) { + fcmService.setUpPushNotifications(); + fcmService.sendFcmTokenToServer(); + } + }); + return userId ? <NavigationBar /> : <Onboarding />; }; diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index 663aeaea..74993af9 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -14,6 +14,9 @@ export type MainStackParams = { Search: { screenType: ScreenType; }; + RequestContactsAccess: { + screenType: ScreenType; + }; Profile: { userXId: string | undefined; screenType: ScreenType; diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx index 3e425101..c0cef3ea 100644 --- a/src/routes/main/MainStackScreen.tsx +++ b/src/routes/main/MainStackScreen.tsx @@ -1,6 +1,7 @@ +import AsyncStorage from '@react-native-community/async-storage'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationOptions} from '@react-navigation/stack'; -import React from 'react'; +import React, {useState} from 'react'; import { CaptionScreen, CategorySelection, @@ -12,6 +13,7 @@ import { MomentUploadPromptScreen, NotificationsScreen, ProfileScreen, + RequestContactsAccess, SearchScreen, SocialMediaTaggs, } from '../../screens'; @@ -42,6 +44,14 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => { const isSearchTab = screenType === ScreenType.Search; const isNotificationsTab = screenType === ScreenType.Notifications; + AsyncStorage.getItem('respondedToAccessContacts').then((value) => + setRespondedToAccessContacts(value ? value : 'false'), + ); + + const [respondedToAccessContacts, setRespondedToAccessContacts] = useState( + 'false', + ); + const initialRouteName = (() => { switch (screenType) { case ScreenType.Profile: @@ -90,13 +100,20 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => { screenType, }} /> - {isSearchTab && ( - <MainStack.Screen - name="Search" - component={SearchScreen} - initialParams={{screenType}} - /> - )} + {isSearchTab && + (respondedToAccessContacts && respondedToAccessContacts === 'true' ? ( + <MainStack.Screen + name="Search" + component={SearchScreen} + initialParams={{screenType}} + /> + ) : ( + <MainStack.Screen + name="Search" + component={RequestContactsAccess} + initialParams={{screenType}} + /> + ))} {isNotificationsTab && ( <MainStack.Screen name="Notifications" @@ -180,9 +197,6 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => { <MainStack.Screen name="FriendsListScreen" component={FriendsListScreen} - options={{ - ...modalStyle, - }} initialParams={{screenType}} /> <MainStack.Screen diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index d9952aa8..f35bb22c 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -35,6 +35,9 @@ const NotificationsScreen: React.FC = () => { const {notifications} = useSelector( (state: RootState) => state.notifications, ); + + const {user: loggedInUser} = useSelector((state: RootState) => state.user); + const [sectionedNotifications, setSectionedNotifications] = useState< {title: 'Today' | 'Yesterday' | 'This Week'; data: NotificationType[]}[] >([]); @@ -130,7 +133,7 @@ const NotificationsScreen: React.FC = () => { <Notification item={item} screenType={ScreenType.Notifications} - moments={item.notification_type === 'CMT' ? loggedInUserMoments : []} + loggedInUser={loggedInUser} /> ); diff --git a/src/screens/onboarding/CategorySelection.tsx b/src/screens/onboarding/CategorySelection.tsx index a3acbbb7..9d5fbe4d 100644 --- a/src/screens/onboarding/CategorySelection.tsx +++ b/src/screens/onboarding/CategorySelection.tsx @@ -17,7 +17,7 @@ import {Background, MomentCategory} from '../../components'; import {MOMENT_CATEGORIES} from '../../constants'; import {ERROR_SOMETHING_WENT_WRONG} from '../../constants/strings'; import {OnboardingStackParams} from '../../routes'; -import {fcmService, postMomentCategories} from '../../services'; +import {postMomentCategories} from '../../services'; import { updateIsOnboardedUser, updateMomentCategories, @@ -169,7 +169,6 @@ const CategorySelection: React.FC<CategorySelectionProps> = ({ const token = await getTokenOrLogout(dispatch); await postMomentCategories(selectedCategories, token); userLogin(dispatch, {userId: userId, username: username}); - fcmService.sendFcmTokenToServer(); } else { dispatch( updateMomentCategories( @@ -309,7 +308,7 @@ const styles = StyleSheet.create({ height: 40, borderRadius: 5, borderWidth: 1, - borderColor: '#8F01FF', + borderColor: 'white', marginBottom: '25%', }, finalActionLabel: { diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx index 127cd9cd..26ad93a7 100644 --- a/src/screens/onboarding/ProfileOnboarding.tsx +++ b/src/screens/onboarding/ProfileOnboarding.tsx @@ -26,6 +26,7 @@ import { bioRegex, genderRegex, CLASS_YEAR_LIST, + TAGG_PURPLE, } from '../../constants'; import AsyncStorage from '@react-native-community/async-storage'; import {BackgroundGradientType} from '../../types'; @@ -549,7 +550,7 @@ const styles = StyleSheet.create({ borderRadius: 55, }, submitBtn: { - backgroundColor: '#8F01FF', + backgroundColor: TAGG_PURPLE, justifyContent: 'center', alignItems: 'center', width: SCREEN_WIDTH / 2.5, diff --git a/src/screens/profile/FriendsListScreen.tsx b/src/screens/profile/FriendsListScreen.tsx index f7192d10..26d19e60 100644 --- a/src/screens/profile/FriendsListScreen.tsx +++ b/src/screens/profile/FriendsListScreen.tsx @@ -1,14 +1,18 @@ -import React, {useState} from 'react'; -import {RouteProp} from '@react-navigation/native'; -import {TabsGradient, Friends, CenteredView} from '../../components'; -import {ScrollView} from 'react-native-gesture-handler'; -import {SCREEN_HEIGHT} from '../../utils'; -import {StyleSheet, View} from 'react-native'; +import React from 'react'; +import {RouteProp, useNavigation} from '@react-navigation/native'; +import {TabsGradient, Friends} from '../../components'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import { + SafeAreaView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; import {ProfileStackParams} from '../../routes'; -import {ProfilePreviewType} from '../../types'; -import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates'; import {useSelector} from 'react-redux'; import {RootState} from '../../store/rootReducer'; +import BackIcon from '../../assets/icons/back-arrow.svg'; type FriendsListScreenRouteProp = RouteProp< ProfileStackParams, @@ -20,51 +24,66 @@ interface FriendsListScreenProps { const FriendsListScreen: React.FC<FriendsListScreenProps> = ({route}) => { const {userXId, screenType} = route.params; + const navigation = useNavigation(); const {friends} = userXId ? useSelector((state: RootState) => state.userX[screenType][userXId]) : useSelector((state: RootState) => state.friends); return ( - <CenteredView> - <View style={styles.modalView}> - <ScrollView - keyboardShouldPersistTaps={'always'} - stickyHeaderIndices={[4]} - contentContainerStyle={styles.contentContainer} - showsVerticalScrollIndicator={false}> - <Friends result={friends} screenType={screenType} /> - </ScrollView> - <TabsGradient /> - </View> - </CenteredView> + <View style={styles.background}> + <SafeAreaView> + <View style={styles.header}> + <TouchableOpacity + style={styles.headerButton} + onPress={() => { + navigation.pop(); + }}> + <BackIcon height={'100%'} width={'100%'} color={'white'} /> + </TouchableOpacity> + <Text style={styles.headerText}>Friends</Text> + </View> + <View style={styles.body}> + <Friends result={friends} screenType={screenType} userId={userXId} /> + </View> + </SafeAreaView> + <TabsGradient /> + </View> ); }; const styles = StyleSheet.create({ - contentContainer: { - paddingBottom: SCREEN_HEIGHT / 15, - paddingHorizontal: 15, - marginTop: '5%', + background: { + backgroundColor: 'white', + height: '100%', }, - modalView: { - width: '85%', - height: '70%', - backgroundColor: '#fff', - shadowColor: '#000', - shadowOpacity: 30, - shadowOffset: {width: 0, height: 2}, - shadowRadius: 5, - borderRadius: 8, - paddingBottom: 15, - paddingHorizontal: 20, - justifyContent: 'space-between', - }, - modalScrollViewContent: { + header: { + flexDirection: 'column', justifyContent: 'center', + height: SCREEN_HEIGHT * 0.05, + padding: '3%', + paddingBottom: 0, + marginTop: '1%', + }, + headerText: { + position: 'absolute', + alignSelf: 'center', + fontSize: normalize(18), + fontWeight: '700', + lineHeight: normalize(21.48), + letterSpacing: normalize(1.3), + }, + headerButton: { + width: '5%', + aspectRatio: 1, + padding: 0, + marginLeft: '5%', + alignSelf: 'flex-start', + marginBottom: '1%', }, - modalScrollView: { - marginBottom: 10, + body: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT * 0.8, }, }); diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index 5c3b8579..ec193db5 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -9,7 +9,7 @@ import CommentsContainer from '../../components/comments/CommentsContainer'; import {ADD_COMMENT_TEXT} from '../../constants/strings'; import {MainStackParams} from '../../routes/main'; import {CommentType} from '../../types'; -import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; /** * Comments Screen for an image uploaded @@ -92,8 +92,10 @@ const styles = StyleSheet.create({ headerText: { position: 'absolute', alignSelf: 'center', - fontSize: 20.5, - fontWeight: '600', + fontSize: normalize(18), + fontWeight: '700', + lineHeight: normalize(21.48), + letterSpacing: normalize(1.3), }, headerButton: { width: '5%', diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx index 0ea96cd2..9cdba555 100644 --- a/src/screens/profile/ProfileScreen.tsx +++ b/src/screens/profile/ProfileScreen.tsx @@ -29,9 +29,9 @@ const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => { * This is a double safety check to avoid app crash. * Checks if the required userXId is present in the store, if not userXId is set to dummy id */ - if (userXId && !(userXId in useStore().getState().userX[screenType])) { - userXId = DUMMY_USERID; - } + // if (userXId && !(userXId in useStore().getState().userX[screenType])) { + // userXId = DUMMY_USERID; + // } /** * Code under useFocusEffect gets executed every time the screen comes under focus / is being viewed by the user. diff --git a/src/screens/search/RequestContactsAccess.tsx b/src/screens/search/RequestContactsAccess.tsx new file mode 100644 index 00000000..de023464 --- /dev/null +++ b/src/screens/search/RequestContactsAccess.tsx @@ -0,0 +1,186 @@ +import * as React from 'react'; +import { + StyleSheet, + View, + Text, + Image, + TouchableOpacity, + StatusBar, +} from 'react-native'; +import {BACKGROUND_GRADIENT_MAP} from '../../constants'; +import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {BackgroundGradientType} from '../../types'; +import {useNavigation} from '@react-navigation/native'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import Animated from 'react-native-reanimated'; +import LinearGradient from 'react-native-linear-gradient'; +import {checkPermission, requestPermission} from 'react-native-contacts'; +import AsyncStorage from '@react-native-community/async-storage'; + +const RequestContactsAccess: React.FC = () => { + const navigation = useNavigation(); + + const handleAllowAccess = async () => { + checkPermission().then((permission) => { + if (permission === 'undefined') { + requestPermission().then((response) => { + if (response === 'authorized' || response === 'denied') { + navigation.navigate('Search'); + } + }); + } + }); + await AsyncStorage.setItem('respondedToAccessContacts', 'true'); + }; + + const handleDontAllowAccess = async () => { + await AsyncStorage.setItem('respondedToAccessContacts', 'true'); + navigation.navigate('Search'); + }; + + return ( + <LinearGradient + colors={BACKGROUND_GRADIENT_MAP[BackgroundGradientType.Light]} + useAngle={true} + angle={154.72} + angleCenter={{x: 0.5, y: 0.5}} + style={{flex: 1}}> + <SafeAreaView> + <View style={{height: SCREEN_HEIGHT}}> + <Animated.ScrollView + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + scrollEnabled={isIPhoneX() ? false : true}> + <StatusBar barStyle="light-content" translucent={false} /> + <View style={styles.mainContainer}> + <Image + source={require('../../assets/icons/findFriends/find-friend-icon.png')} + style={styles.image} + /> + <Text style={styles.title}>FIND FRIENDS!</Text> + <Text style={styles.subtext}> + This is so you can find your friends already on here! Isn’t a + party better when your favorite people are there? + </Text> + <View style={styles.bulletPointView}> + <Image + source={require('../../assets/icons/findFriends/lock-icon.png')} + style={styles.icon} + /> + <Text style={styles.bulletPointText}>Always Stays Private</Text> + </View> + <View style={styles.bulletPointView}> + <Image + source={require('../../assets/icons/findFriends/phone-cross-icon.png')} + style={styles.icon} + /> + <Text style={styles.bulletPointText}> + We wouldn’t dare send any messages + </Text> + </View> + <TouchableOpacity + onPress={handleAllowAccess} + style={styles.allowButton}> + <Text style={styles.allowButtonLabel}>Allow Contacts</Text> + </TouchableOpacity> + <TouchableOpacity + accessibilityLabel="Don't allow button" + style={styles.dontAllowButton} + onPress={handleDontAllowAccess}> + <Text style={styles.dontAllowButtonText}>Don’t Allow</Text> + </TouchableOpacity> + </View> + </Animated.ScrollView> + </View> + </SafeAreaView> + </LinearGradient> + ); +}; + +const styles = StyleSheet.create({ + mainContainer: { + flex: 1, + flexDirection: 'column', + alignContent: 'center', + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + marginBottom: '15%', + }, + image: { + marginBottom: '2%', + width: SCREEN_WIDTH, + height: SCREEN_WIDTH * 0.49, + }, + title: { + color: '#fff', + alignSelf: 'center', + fontSize: normalize(28), + lineHeight: normalize(35), + fontWeight: '600', + textAlign: 'center', + marginBottom: '2%', + }, + subtext: { + color: '#fff', + alignSelf: 'center', + fontSize: normalize(16), + lineHeight: normalize(25), + fontWeight: '600', + textAlign: 'center', + width: '83%', + height: '15%', + }, + bulletPointView: { + flexDirection: 'row', + justifyContent: 'space-between', + alignSelf: 'center', + width: SCREEN_WIDTH * 0.55, + marginBottom: '7%', + }, + icon: { + margin: '1%', + width: normalize(38), + height: normalize(38), + alignSelf: 'flex-start', + }, + bulletPointText: { + color: '#fff', + fontSize: normalize(15), + fontWeight: '500', + lineHeight: normalize(20), + alignSelf: 'center', + width: '75%', + textAlign: 'center', + }, + allowButton: { + backgroundColor: '#fff', + justifyContent: 'center', + alignItems: 'center', + alignSelf: 'center', + width: '41.5%', + height: '6%', + borderRadius: 5, + borderWidth: 1, + borderColor: '#fff', + marginTop: '8%', + marginBottom: '3%', + }, + allowButtonLabel: { + fontSize: normalize(17), + fontWeight: '600', + lineHeight: normalize(20.29), + color: '#3C4461', + }, + dontAllowButton: { + alignSelf: 'center', + borderBottomWidth: 1, + borderBottomColor: 'white', + }, + dontAllowButtonText: { + fontSize: normalize(15), + fontWeight: '500', + lineHeight: normalize(20), + color: '#fff', + }, +}); +export default RequestContactsAccess; diff --git a/src/screens/search/index.ts b/src/screens/search/index.ts index b6680aa4..d5eb9c3e 100644 --- a/src/screens/search/index.ts +++ b/src/screens/search/index.ts @@ -1 +1,2 @@ export {default as SearchScreen} from './SearchScreen'; +export {default as RequestContactsAccess} from './RequestContactsAccess'; diff --git a/src/services/UserFriendsService.ts b/src/services/UserFriendsService.ts index f2e15824..99d86d0b 100644 --- a/src/services/UserFriendsService.ts +++ b/src/services/UserFriendsService.ts @@ -147,3 +147,32 @@ export const acceptFriendRequestService = async ( return false; } }; + +export const unfriendService = async ( + user_id: string, + token: string | null, +) => { + try { + const response = await fetch(FRIENDS_ENDPOINT + `${user_id}/`, { + method: 'DELETE', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + reason: 'unfriended', + }), + }); + const status = response.status; + if (Math.floor(status / 100) === 2) { + return true; + } else { + console.log(await response.json()); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + return false; + } + } catch (error) { + console.log(error); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); + return false; + } +}; diff --git a/src/store/actions/userFriends.ts b/src/store/actions/userFriends.ts index 18ad247c..763f2575 100644 --- a/src/store/actions/userFriends.ts +++ b/src/store/actions/userFriends.ts @@ -11,6 +11,7 @@ import { declineFriendRequestService, friendOrUnfriendUser, loadFriends, + unfriendService, } from '../../services'; import {Action, ThunkAction} from '@reduxjs/toolkit'; import { @@ -61,15 +62,17 @@ export const friendUnfriendUser = ( data = 'requested'; break; case 'requested': // cancel request: update to no relationship + break; case 'friends': // unfriend: update to no relationship dispatch({ type: updateFriends.type, payload: { - friend, + data: friend, isFriend: true, }, }); data = 'no_record'; + break; } dispatch({ type: userXFriendshipEdited.type, @@ -85,6 +88,39 @@ export const friendUnfriendUser = ( } }; +export const unfriendUser = ( + friend: ProfilePreviewType, // userX's profile preview + screenType: ScreenType, //screentype from content +): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async ( + dispatch, +) => { + try { + const token = await getTokenOrLogout(dispatch); + // Calls method to send post or delete request + const success = await unfriendService(friend.id, token); + if (success) { + let data = 'no_record'; + await dispatch({ + type: updateFriends.type, + payload: { + data: friend, + isFriend: true, + }, + }); + await dispatch({ + type: userXFriendshipEdited.type, + payload: { + userId: friend.id, + screenType, + data, + }, + }); + } + } catch (error) { + console.log(error); + } +}; + export const acceptFriendRequest = ( requester: ProfilePreviewType, ): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async ( diff --git a/src/store/reducers/userFriendsReducer.ts b/src/store/reducers/userFriendsReducer.ts index 2041a181..92402db1 100644 --- a/src/store/reducers/userFriendsReducer.ts +++ b/src/store/reducers/userFriendsReducer.ts @@ -11,8 +11,14 @@ const userFriendsSlice = createSlice({ updateFriends: (state, action) => { const {isFriend, data} = action.payload; - if (!isFriend) state.friends.push(data); - else { + if (!isFriend) { + const friendInList: boolean = state.friends.some( + (friend) => friend.username === data.username, + ); + if (!friendInList) { + state.friends.push(data); + } + } else { state.friends = state.friends.filter( (friend) => friend.username !== data.username, ); diff --git a/src/types/types.ts b/src/types/types.ts index f1ba12f4..ab995292 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -87,7 +87,6 @@ export interface MomentType { moment_url: string; thumbnail_url: string; } - export interface CommentBaseType { comment_id: string; comment: string; @@ -165,7 +164,7 @@ export enum CategorySelectionScreenType { export enum BackgroundGradientType { Light, Dark, - Notification + Notification, } /** @@ -177,11 +176,28 @@ export type TaggPopupType = { next?: TaggPopupType; }; +export interface MomentWithUserType extends MomentType { + user: ProfilePreviewType; +} + +export interface CommentNotificationType { + comment_id: string; + notification_data: MomentWithUserType; +} + +export interface ThreadNotificationType extends CommentNotificationType { + parent_comment: string; +} + export type NotificationType = { actor: ProfilePreviewType; verbage: string; notification_type: TypeOfNotification; - notification_object: CommentType | CommentThreadType | MomentType | undefined; + notification_object: + | CommentNotificationType + | ThreadNotificationType + | MomentType + | undefined; timestamp: string; unread: boolean; }; @@ -196,7 +212,7 @@ export type TypeOfNotification = | 'FRD_ACPT' // notification_object is undefined | 'FRD_DEC' - // notification_object is CommentType || CommentThreadType + // notification_object is CommentNotificationType || ThreadNotificationType | 'CMT' // notification_object is MomentType | 'MOM_3+' diff --git a/src/utils/friends.ts b/src/utils/friends.ts new file mode 100644 index 00000000..ba15e087 --- /dev/null +++ b/src/utils/friends.ts @@ -0,0 +1,54 @@ +// Handles click on friend/requested/unfriend button + +import {RootState} from '../store/rootReducer'; +import {ProfilePreviewType, ProfileType, ScreenType, UserType} from '../types'; +import {AppDispatch} from '../store/configureStore'; +import { + friendUnfriendUser, + unfriendUser, + updateUserXFriends, + updateUserXProfileAllScreens, +} from '../store/actions'; +import {getUserAsProfilePreviewType} from './users'; + +/* + * 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 + */ +export const handleFriendUnfriend = async ( + screenType: ScreenType, + user: UserType, + profile: ProfileType, + dispatch: AppDispatch, + state: RootState, + loggedInUser: UserType, +) => { + const {friendship_status} = profile; + await dispatch( + friendUnfriendUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + friendship_status, + screenType, + ), + ); + await dispatch(updateUserXFriends(user.userId, state)); + dispatch(updateUserXProfileAllScreens(user.userId, state)); +}; + +export const handleUnfriend = async ( + screenType: ScreenType, + friend: ProfilePreviewType, + dispatch: AppDispatch, + state: RootState, +) => { + await dispatch(unfriendUser(friend, screenType)); + await dispatch(updateUserXFriends(friend.id, state)); + dispatch(updateUserXProfileAllScreens(friend.id, state)); +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 629a0091..82c94100 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,3 +2,4 @@ export * from './layouts'; export * from './moments'; export * from './common'; export * from './users'; +export * from './friends'; |