diff options
39 files changed, 896 insertions, 412 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/TaggPopup.tsx b/src/components/common/TaggPopup.tsx index 86a472b1..b5ac32ec 100644 --- a/src/components/common/TaggPopup.tsx +++ b/src/components/common/TaggPopup.tsx @@ -7,6 +7,7 @@ import {ArrowButton} from '..'; import {OnboardingStackParams} from '../../routes'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import CloseIcon from '../../assets/ionicons/close-outline.svg'; +import {BlurView} from '@react-native-community/blur'; type TaggPopupRouteProps = RouteProp<OnboardingStackParams, 'TaggPopup'>; type TaggPopupNavigationProps = StackNavigationProp< @@ -31,41 +32,43 @@ const TaggPopup: React.FC<TaggPopupProps> = ({route, navigation}) => { const {messageHeader, messageBody, next} = route.params.popupProps; return ( - <TouchableOpacity - style={styles.container} - onPressOut={() => { - navigation.goBack(); - }}> - <View style={styles.popup}> - <Image - style={styles.icon} - source={require('../../assets/icons/plus-logo.png')} - /> - <View style={styles.textContainer}> - <Text style={styles.header}>{messageHeader}</Text> - <Text style={styles.subtext}>{messageBody}</Text> - </View> - {!next && ( - <TouchableOpacity - style={styles.closeButton} - onPress={() => { - navigation.goBack(); - }}> - <CloseIcon height={'50%'} width={'50%'} color={'white'} /> - </TouchableOpacity> - )} - </View> - {next && ( - <View style={styles.footer}> - <ArrowButton - direction="forward" - onPress={() => { - navigation.navigate('TaggPopup', {popupProps: next}); - }} + <BlurView blurType="light" blurAmount={2} style={styles.container}> + <TouchableOpacity + style={styles.container} + onPressOut={() => { + navigation.goBack(); + }}> + <View style={styles.popup}> + <Image + style={styles.icon} + source={require('../../assets/icons/plus-logo.png')} /> + <View style={styles.textContainer}> + <Text style={styles.header}>{messageHeader}</Text> + <Text style={styles.subtext}>{messageBody}</Text> + </View> + {!next && ( + <TouchableOpacity + style={styles.closeButton} + onPress={() => { + navigation.goBack(); + }}> + <CloseIcon height={'50%'} width={'50%'} color={'white'} /> + </TouchableOpacity> + )} </View> - )} - </TouchableOpacity> + {next && ( + <View style={styles.footer}> + <ArrowButton + direction="forward" + onPress={() => { + navigation.navigate('TaggPopup', {popupProps: next}); + }} + /> + </View> + )} + </TouchableOpacity> + </BlurView> ); }; @@ -75,6 +78,8 @@ const styles = StyleSheet.create({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', + width: '100%', + height: '100%', }, whiteColor: { color: 'white', 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/moments/Moment.tsx b/src/components/moments/Moment.tsx index 6dbcd170..7905e8a9 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -1,27 +1,20 @@ import {useNavigation} from '@react-navigation/native'; import React, {Fragment} from 'react'; -import { - Alert, - StyleProp, - StyleSheet, - View, - ViewProps, - ViewStyle, -} from 'react-native'; +import {Alert, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; import {Text} from 'react-native-animatable'; import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler'; +import ImagePicker from 'react-native-image-crop-picker'; import LinearGradient from 'react-native-linear-gradient'; -import PlusIcon from '../../assets/icons/plus_icon-01.svg'; -import UpIcon from '../../assets/icons/up_icon.svg'; -import DownIcon from '../../assets/icons/down_icon.svg'; +import {MomentType, ScreenType} from 'src/types'; import DeleteIcon from '../../assets/icons/delete-logo.svg'; +import DownIcon from '../../assets/icons/down_icon.svg'; +import PlusIcon from '../../assets/icons/plus_icon-01.svg'; import BigPlusIcon from '../../assets/icons/plus_icon-02.svg'; +import UpIcon from '../../assets/icons/up_icon.svg'; import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; +import {ERROR_UPLOAD_MOMENT_SHORT} from '../../constants/strings'; import {SCREEN_WIDTH} from '../../utils'; -import ImagePicker from 'react-native-image-crop-picker'; import MomentTile from './MomentTile'; -import {MomentType, ScreenType} from 'src/types'; -import {useDispatch} from 'react-redux'; interface MomentProps { title: string; @@ -49,7 +42,6 @@ const Moment: React.FC<MomentProps> = ({ externalStyles, }) => { const navigation = useNavigation(); - const dispatch = useDispatch(); const navigateToImagePicker = () => { ImagePicker.openPicker({ @@ -77,7 +69,7 @@ const Moment: React.FC<MomentProps> = ({ }) .catch((err) => { if (err.code && err.code !== 'E_PICKER_CANCELLED') { - Alert.alert('Unable to upload moment!'); + Alert.alert(ERROR_UPLOAD_MOMENT_SHORT); } }); }; diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx index 184e3f27..e6d16f82 100644 --- a/src/components/notifications/Notification.tsx +++ b/src/components/notifications/Notification.tsx @@ -1,35 +1,30 @@ import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; -import { - ActivityIndicatorBase, - Alert, - Image, - StyleSheet, - Text, - View, -} from 'react-native'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import {Button} from 'react-native-elements'; import {TouchableWithoutFeedback} from 'react-native-gesture-handler'; -import {useDispatch, useSelector, useStore} from 'react-redux'; -import {MomentCommentsScreen} from '../../screens'; -import {loadAvatar} from '../../services'; +import {useDispatch, useStore} from 'react-redux'; import { - EMPTY_MOMENTS_LIST, - EMPTY_MOMENT_CATEGORIES, -} from '../../store/initialStates'; -import {userSocialsReducer} from '../../store/reducers'; -import {RootState} from '../../store/rootReducer'; -import {NotificationType, ScreenType} from '../../types'; + 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'; + interface NotificationProps { item: NotificationType; - userXId: string | undefined; screenType: ScreenType; + moments: MomentType[]; } const Notification: React.FC<NotificationProps> = (props) => { @@ -41,21 +36,17 @@ const Notification: React.FC<NotificationProps> = (props) => { notification_object, unread, }, - userXId, screenType, + moments: loggedInUserMoments, } = props; + const navigation = useNavigation(); const state: RootState = useStore().getState(); const dispatch = useDispatch(); - const {moments: loggedInUserMoments} = - notification_type === 'CMT' - ? useSelector((state: RootState) => state.moments) - : {moments: undefined}; 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) => { @@ -70,24 +61,26 @@ const Notification: React.FC<NotificationProps> = (props) => { }; }, [id]); - // TODO: this should be moment thumbnail, waiting for that to complete - // useEffect(() => { - // let mounted = true; - // const loadMomentImage = async (user_id: string) => { - // const response = await loadAvatar(user_id, true); - // if (mounted) { - // setMomentURI(response); - // } - // }; - // loadMomentImage(id); - // return () => { - // mounted = false; - // }; - // }, [id, notification_object]); + useEffect(() => { + let mounted = true; + const loadMomentImage = async (moment_id: string) => { + const response = await loadMomentThumbnail(moment_id); + if (mounted && response) { + setMomentURI(response); + } + }; + if (notification_type === 'CMT' && notification_object) { + loadMomentImage(notification_object.moment_id); + return () => { + mounted = false; + }; + } + }, [id, notification_object, notification_type]); const onNotificationTap = async () => { switch (notification_type) { - case 'FRD': + case 'FRD_ACPT': + case 'FRD_REQ': if (!userXInStore(state, screenType, id)) { await fetchUserX( dispatch, @@ -108,7 +101,7 @@ const Notification: React.FC<NotificationProps> = (props) => { if (moment) { navigation.push('IndividualMoment', { moment, - userXId, + userXId: undefined, // we're only viewing our own moment here screenType, }); setTimeout(() => { @@ -124,34 +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, 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> - {/* TODO: Still WIP */} - {/* {notification_type === 'CMT' && notification_object && ( - <Image - style={styles.moment} - source={{uri: momentURI, cache: 'only-if-cached'}} - /> - )} */} - </TouchableWithoutFeedback> + <> + <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> + </> ); }; @@ -163,7 +174,7 @@ const styles = StyleSheet.create({ alignItems: 'center', }, avatarContainer: { - marginLeft: '5%', + marginLeft: '8%', flex: 1, justifyContent: 'center', }, @@ -178,7 +189,7 @@ const styles = StyleSheet.create({ height: '80%', flexDirection: 'column', justifyContent: 'space-around', - marginRight: SCREEN_WIDTH / 6, + marginRight: '15%', }, actorName: { fontSize: 15, @@ -190,6 +201,7 @@ const styles = StyleSheet.create({ width: 42, right: '5%', }, + buttonsContainer: {}, }); export default Notification; diff --git a/src/components/onboarding/LinkSocialMedia.tsx b/src/components/onboarding/LinkSocialMedia.tsx index c7b0a6b4..6cb7e9cf 100644 --- a/src/components/onboarding/LinkSocialMedia.tsx +++ b/src/components/onboarding/LinkSocialMedia.tsx @@ -13,6 +13,7 @@ import { SOCIAL_FONT_COLORS, TAGG_ICON_DIM, } from '../../constants/constants'; +import {ERROR_LINK, SUCCESS_LINK} from '../../constants/strings'; import { handlePressForAuthBrowser, registerNonIntegratedSocialLink, @@ -64,12 +65,12 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ const linkNonIntegratedSocial = async (username: string) => { if (await registerNonIntegratedSocialLink(label, username)) { - Alert.alert(`Successfully linked ${label} 🎉`); + Alert.alert(SUCCESS_LINK(label)); setAuthenticated(true); } else { // If we display too fast the alert will get dismissed with the modal setTimeout(() => { - Alert.alert(`Something went wrong, we can't link with ${label} 😔`); + Alert.alert(ERROR_LINK(label)); }, 500); } }; diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 1d639a41..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)); }; /** @@ -296,7 +284,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => { momentCategories.filter((mc) => mc !== category), false, ), - ); + ) dispatch(deleteUserMomentsForCategory(category)); }, }, diff --git a/src/components/profile/MomentMoreInfoDrawer.tsx b/src/components/profile/MomentMoreInfoDrawer.tsx index e127e05c..77c349ca 100644 --- a/src/components/profile/MomentMoreInfoDrawer.tsx +++ b/src/components/profile/MomentMoreInfoDrawer.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {Alert, StyleSheet, TouchableOpacity, ViewProps} from 'react-native'; import MoreIcon from '../../assets/icons/more_horiz-24px.svg'; +import {ERROR_DELETE_MOMENT, MOMENT_DELETED_MSG} from '../../constants/strings'; import {deleteMoment, sendReport} from '../../services'; import {GenericMoreInfoDrawer} from '../common'; @@ -21,7 +22,7 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { if (success) { // set time out for UI transitions setTimeout(() => { - Alert.alert('Moment deleted!', '', [ + Alert.alert(MOMENT_DELETED_MSG, '', [ { text: 'OK', onPress: () => dismissScreenAndUpdate(), @@ -31,9 +32,7 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { }, 500); } else { setTimeout(() => { - Alert.alert( - 'We were unable to delete that moment 😠, please try again later!', - ); + Alert.alert(ERROR_DELETE_MOMENT); }, 500); } }); diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index f4711300..6284ff59 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, @@ -100,6 +173,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 134e94cd..e6311daa 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -18,9 +18,10 @@ 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'; const NO_USER: UserType = { userId: '', @@ -72,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 () => { @@ -92,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('You cannot view this profile'); - return; - } const jsonValue = await AsyncStorage.getItem( '@recently_searched_users', ); diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx index 12172df9..82ac07df 100644 --- a/src/components/taggs/Tagg.tsx +++ b/src/components/taggs/Tagg.tsx @@ -17,6 +17,11 @@ import { } from '../../services'; import {SmallSocialIcon, SocialIcon, SocialLinkModal} from '../common'; import {UserType} from '../../types'; +import { + ERROR_LINK, + ERROR_UNABLE_TO_FIND_PROFILE, + SUCCESS_LINK, +} from '../../constants/strings'; interface TaggProps { social: string; @@ -56,7 +61,7 @@ const Tagg: React.FC<TaggProps> = ({ show auth browser case !integrated_social: show modal - Tagg's "Tagg" will use the Ring instead of PurpleRing + Tagg's "Tagg" will use the Ring instead of PurpleRing */ const modalOrAuthBrowserOrPass = async () => { @@ -71,7 +76,7 @@ const Tagg: React.FC<TaggProps> = ({ if (socialURL) { Linking.openURL(socialURL); } else { - Alert.alert('We were unable to find this profile 😔'); + Alert.alert(ERROR_UNABLE_TO_FIND_PROFILE); } }); } @@ -79,7 +84,9 @@ const Tagg: React.FC<TaggProps> = ({ if (isIntegrated) { handlePressForAuthBrowser(social).then((success) => { setTaggsNeedUpdate(success); - if (success) setSocialDataNeedUpdate(social, ''); + if (success) { + setSocialDataNeedUpdate(social, ''); + } }); } else { setModalVisible(true); @@ -105,13 +112,13 @@ const Tagg: React.FC<TaggProps> = ({ const linkNonIntegratedSocial = async (username: string) => { if (await registerNonIntegratedSocialLink(social, username)) { - Alert.alert(`Successfully linked ${social} 🎉`); + Alert.alert(SUCCESS_LINK(social)); setTaggsNeedUpdate(true); setSocialDataNeedUpdate(social, username); } else { // If we display too fast the alert will get dismissed with the modal setTimeout(() => { - Alert.alert(`Something went wrong, we can't link with ${social} 😔`); + Alert.alert(ERROR_LINK(social)); }, 500); } }; diff --git a/src/constants/api.ts b/src/constants/api.ts index de43b94d..701070eb 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -18,6 +18,7 @@ export const GET_FB_POSTS_ENDPOINT: string = API_URL + 'posts-fb/'; export const GET_TWITTER_POSTS_ENDPOINT: string = API_URL + 'posts-twitter/'; export const SEARCH_ENDPOINT: string = API_URL + 'search/'; export const MOMENTS_ENDPOINT: string = API_URL + 'moments/'; +export const MOMENT_THUMBNAIL_ENDPOINT: string = API_URL + 'moment-thumbnail/'; export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/'; export const COMMENTS_ENDPOINT: string = API_URL + 'comments/'; export const FRIENDS_ENDPOINT: string = API_URL + 'friends/'; diff --git a/src/constants/strings.ts b/src/constants/strings.ts new file mode 100644 index 00000000..b5344afd --- /dev/null +++ b/src/constants/strings.ts @@ -0,0 +1,47 @@ +/* eslint-disable */ +// Below is the regex to convert this into a csv for the Google Sheet +// export const (.*) = .*?(['|"|`])(.*)\2; +// replace with: $1\t$3 +export const COMING_SOON_MSG = 'Creating more fun things for you, surprises coming soon 😉'; +export const ERROR_AUTHENTICATION = 'An error occurred during authentication. Please login again!'; +export const ERROR_CATEGORY_CREATION = 'There was a problem creating your categories. Please refresh and try again.'; +export const ERROR_CATEGORY_UPDATE = 'There was a problem updating your categories. Please refresh and try again'; +export const ERROR_DELETE_CATEGORY = 'There was a problem while deleting category. Please try again'; +export const ERROR_DELETE_MOMENT = 'Unable to delete moment, please try again later!'; +export const ERROR_DOUBLE_CHECK_CONNECTION = 'Please double-check your network connection and retry'; +export const ERROR_DUP_OLD_PWD = 'You may not use a previously used password'; +export const ERROR_EMAIL_IN_USE = 'Email already in use, please try another one'; +export const ERROR_FAILED_LOGIN_INFO = 'Login failed, please try re-entering your login information'; +export const ERROR_FAILED_TO_COMMENT = 'Unable to post comment, refresh and try again!'; +export const ERROR_INVALID_INVITATION_CODE = 'Invitation code invalid, try again or talk to the friend that sent it 😬'; +export const ERROR_INVALID_LOGIN = 'Invalid login, Please login again'; +export const ERROR_INVALID_PWD_CODE = 'Looks like you have entered the wrong code, please try again'; +export const ERROR_INVALID_VERIFICATION_CODE = 'Invalid verification code, try re-entering or tap the resend code button for a new code'; +export const ERROR_INVALID_VERIFICATION_CODE_FORMAT = 'Please enter the 6 digit code sent to your phone'; +export const ERROR_INVLAID_CODE = 'The code entered is not valid!'; +export const ERROR_LINK = (str: string) => `Unable to link with ${str}, Please check your login and try again`; +export const ERROR_LOGIN = 'There was a problem logging you in, please refresh and try again'; +export const ERROR_LOGIN_FAILED = 'Login failed. Check your username and passoword, and try again'; +export const ERROR_NEXT_PAGE = 'There was a problem while loading the next page 😓, try again in a couple minutes'; +export const ERROR_PROFILE_CREATION_SHORT = 'Profile creation failed 😓'; +export const ERROR_PWD_ACCOUNT = (str: string) => `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${str}`; +export const ERROR_REGISTRATION = (str: string) => `Registration failed 😔, ${str}`; +export const ERROR_SELECT_CLASS_YEAR = 'Please select your Class Year'; +export const ERROR_SERVER_DOWN = 'mhm, looks like our servers are down, please refresh and try again in a few mins'; +export const ERROR_SOMETHING_WENT_WRONG = "Oh dear, don’t worry someone will be held responsible for this error, In the meantime refresh the app"; +export const ERROR_SOMETHING_WENT_WRONG_REFRESH = "Ha, looks like this one's on us, please refresh and try again"; +export const ERROR_SOMETHING_WENT_WRONG_RELOAD = "You broke it, Just kidding! we don't know what happened... Please reload the app and try again"; +export const ERROR_UNABLE_TO_FIND_PROFILE = 'We were unable to find this profile. Please check username and try again'; +export const ERROR_UNABLE_TO_VIEW_PROFILE = 'Unable to view this profile'; +export const ERROR_UPLOAD = 'An error occurred while uploading. Please try again!'; +export const ERROR_UPLOAD_LARGE_PROFILE_PIC = "Can't have the first image seen on the profile be blank, please upload a large picture"; +export const ERROR_UPLOAD_MOMENT = 'Unable to upload moment. Please retry'; +export const ERROR_UPLOAD_SMALL_PROFILE_PIC = "Can't have a profile without a pic to represent you, please upload a small profile picture"; +export const ERROR_VERIFICATION_FAILED_SHORT = 'Verification failed 😓'; +export const MARKED_AS_MSG = (str: string) => `Marked as ${str}`; +export const MOMENT_DELETED_MSG = 'Moment deleted....Some moments have to go, to create space for greater ones'; +export const SUCCESS_CATEGORY_DELETE = 'Category successfully deleted, but its memory will live on'; +export const SUCCESS_LINK = (str: string) => `Successfully linked ${str} 🎉`; +export const SUCCESS_PIC_UPLOAD = 'Beautiful, the picture was uploaded successfully!'; +export const SUCCESS_PWD_RESET = 'Your password was reset successfully!'; +export const SUCCESS_VERIFICATION_CODE_SENT = 'New verification code sent! Check your phone messages for your code'; diff --git a/src/routes/onboarding/OnboardingStackScreen.tsx b/src/routes/onboarding/OnboardingStackScreen.tsx index afc5be99..78f113cc 100644 --- a/src/routes/onboarding/OnboardingStackScreen.tsx +++ b/src/routes/onboarding/OnboardingStackScreen.tsx @@ -76,14 +76,6 @@ const Onboarding: React.FC = () => { outputRange: [0, 0.25, 0.7, 1], }), }, - overlayStyle: { - backgroundColor: '#505050', - opacity: progress.interpolate({ - inputRange: [0, 1], - outputRange: [0, 0.9], - extrapolate: 'clamp', - }), - }, }), }} /> diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index 8aa47299..219a0be9 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -18,6 +18,9 @@ import {getDateAge, SCREEN_HEIGHT} from '../../utils'; const NotificationsScreen: React.FC = () => { const {user: loggedInUser} = useSelector((state: RootState) => state.user); + const {moments: loggedInUserMoments} = useSelector( + (state: RootState) => state.moments, + ); const [refreshing, setRefreshing] = useState(false); // used for figuring out which ones are unread const [lastViewed, setLastViewed] = useState<moment.Moment | undefined>( @@ -98,10 +101,8 @@ const NotificationsScreen: React.FC = () => { const renderNotification = ({item}: {item: NotificationType}) => ( <Notification item={item} - userXId={ - item.actor.id === loggedInUser.userId ? undefined : item.actor.id - } screenType={ScreenType.Notifications} + moments={item.notification_type === 'CMT' ? loggedInUserMoments : []} /> ); @@ -134,7 +135,7 @@ const NotificationsScreen: React.FC = () => { const styles = StyleSheet.create({ header: { - marginLeft: '5%', + marginLeft: '8%', marginTop: '5%', alignSelf: 'flex-start', flexDirection: 'column', @@ -156,10 +157,10 @@ const styles = StyleSheet.create({ backgroundColor: '#f3f2f2', }, sectionHeader: { - marginLeft: '5%', + marginLeft: '8%', marginTop: '5%', marginBottom: '2%', - fontSize: 16, + fontSize: 15, }, }); diff --git a/src/screens/onboarding/CategorySelection.tsx b/src/screens/onboarding/CategorySelection.tsx index 5589ea9e..a3acbbb7 100644 --- a/src/screens/onboarding/CategorySelection.tsx +++ b/src/screens/onboarding/CategorySelection.tsx @@ -15,11 +15,12 @@ import {useDispatch, useSelector} from 'react-redux'; import PlusIcon from '../../assets/icons/plus_icon-01.svg'; 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 { - updateMomentCategories, updateIsOnboardedUser, + updateMomentCategories, } from '../../store/actions/'; import {RootState} from '../../store/rootReducer'; import {BackgroundGradientType, CategorySelectionScreenType} from '../../types'; @@ -180,7 +181,7 @@ const CategorySelection: React.FC<CategorySelectionProps> = ({ } } catch (error) { console.log(error); - Alert.alert('There was a problem'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); } }; diff --git a/src/screens/onboarding/InvitationCodeVerification.tsx b/src/screens/onboarding/InvitationCodeVerification.tsx index cc7cd678..903a9912 100644 --- a/src/screens/onboarding/InvitationCodeVerification.tsx +++ b/src/screens/onboarding/InvitationCodeVerification.tsx @@ -31,6 +31,12 @@ import { } from 'react-native'; import {BackgroundGradientType} from '../../types'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_INVALID_INVITATION_CODE, + ERROR_INVLAID_CODE, + ERROR_VERIFICATION_FAILED_SHORT, +} from '../../constants/strings'; type InvitationCodeVerificationScreenNavigationProp = StackNavigationProp< OnboardingStackParams, @@ -66,23 +72,20 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ }, ); - if (verifyInviteCodeResponse.status == 200) { + if (verifyInviteCodeResponse.status === 200) { navigation.navigate('RegistrationOne'); } else { - Alert.alert('Invalid invitation code 🤔'); + Alert.alert(ERROR_INVALID_INVITATION_CODE); } } catch (error) { - Alert.alert( - 'Verifiation failed 😓', - 'Please double-check your network connection and retry.', - ); + Alert.alert(ERROR_VERIFICATION_FAILED_SHORT, ERROR_DOUBLE_CHECK_CONNECTION); return { name: 'Verification error', description: error, }; } } else { - Alert.alert('The code entered is not valid!'); + Alert.alert(ERROR_INVLAID_CODE); } }; diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index d1717fc1..8974e000 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -1,30 +1,37 @@ -import React, {useEffect, useRef, useState} from 'react'; +import AsyncStorage from '@react-native-community/async-storage'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useEffect, useRef, useState} from 'react'; import { - View, - Text, Alert, - StatusBar, Image, - TouchableOpacity, - StyleSheet, KeyboardAvoidingView, Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, } from 'react-native'; -import {fcmService} from '../../services'; -import {OnboardingStackParams} from '../../routes/onboarding'; -import {Background, TaggInput, SubmitButton} from '../../components'; +import SplashScreen from 'react-native-splash-screen'; +import {useDispatch} from 'react-redux'; +import {Background, SubmitButton, TaggInput} from '../../components'; import { - usernameRegex, LOGIN_ENDPOINT, TAGG_LIGHT_PURPLE, + usernameRegex, } from '../../constants'; -import AsyncStorage from '@react-native-community/async-storage'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_FAILED_LOGIN_INFO, + ERROR_INVALID_LOGIN, + ERROR_LOGIN_FAILED, + ERROR_SOMETHING_WENT_WRONG_REFRESH, +} from '../../constants/strings'; +import {OnboardingStackParams} from '../../routes/onboarding'; +import {fcmService} from '../../services'; import {BackgroundGradientType, UserType} from '../../types'; -import {useDispatch} from 'react-redux'; import {userLogin} from '../../utils'; -import SplashScreen from 'react-native-splash-screen'; type VerificationScreenRouteProp = RouteProp<OnboardingStackParams, 'Login'>; type VerificationScreenNavigationProp = StackNavigationProp< @@ -167,28 +174,19 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { } catch (err) { setUser(NO_USER); console.log(data); - Alert.alert('Auth token storage failed', 'Please login again!'); + Alert.alert(ERROR_INVALID_LOGIN); } } else if (statusCode === 401) { - Alert.alert( - 'Login failed 😔', - 'Try re-entering your login information.', - ); + Alert.alert(ERROR_FAILED_LOGIN_INFO); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } else { setForm({...form, attemptedSubmit: false}); setTimeout(() => setForm({...form, attemptedSubmit: true})); } } catch (error) { - Alert.alert( - 'Login failed 😓', - 'Please double-check your network connection and retry.', - ); + Alert.alert(ERROR_LOGIN_FAILED, ERROR_DOUBLE_CHECK_CONNECTION); return { name: 'Login error', description: error, diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx index 1f8e58da..127cd9cd 100644 --- a/src/screens/onboarding/ProfileOnboarding.tsx +++ b/src/screens/onboarding/ProfileOnboarding.tsx @@ -32,6 +32,14 @@ import {BackgroundGradientType} from '../../types'; import {PickerSelectProps} from 'react-native-picker-select'; import Animated from 'react-native-reanimated'; import {SCREEN_WIDTH} from '../../utils'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_PROFILE_CREATION_SHORT, + ERROR_SELECT_CLASS_YEAR, + ERROR_SOMETHING_WENT_WRONG_REFRESH, + ERROR_UPLOAD_LARGE_PROFILE_PIC, + ERROR_UPLOAD_SMALL_PROFILE_PIC, +} from '../../constants/strings'; type ProfileOnboardingScreenRouteProp = RouteProp< OnboardingStackParams, @@ -260,15 +268,15 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ const handleSubmit = async () => { if (!form.largePic) { - Alert.alert('Please select a Header image!'); + Alert.alert(ERROR_UPLOAD_LARGE_PROFILE_PIC); return; } if (!form.smallPic) { - Alert.alert('Please select a Profile Picture!'); + Alert.alert(ERROR_UPLOAD_SMALL_PROFILE_PIC); return; } if (form.classYear === -1) { - Alert.alert('Please select Class Year'); + Alert.alert(ERROR_SELECT_CLASS_YEAR); return; } if (!form.attemptedSubmit) { @@ -363,16 +371,10 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({ data.error || 'Something went wrong! ðŸ˜', ); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } catch (error) { - Alert.alert( - 'Profile creation failed 😓', - 'Please double-check your network connection and retry.', - ); + Alert.alert(ERROR_PROFILE_CREATION_SHORT, ERROR_DOUBLE_CHECK_CONNECTION); return { name: 'Profile creation error', description: error, diff --git a/src/screens/onboarding/RegistrationOne.tsx b/src/screens/onboarding/RegistrationOne.tsx index 54c4e210..2a1d884d 100644 --- a/src/screens/onboarding/RegistrationOne.tsx +++ b/src/screens/onboarding/RegistrationOne.tsx @@ -28,6 +28,7 @@ import {SEND_OTP_ENDPOINT} from '../../constants'; import {phoneRegex} from '../../constants'; import {BackgroundGradientType, VerificationScreenType} from '../../types'; +import {ERROR_EMAIL_IN_USE, ERROR_SERVER_DOWN} from '../../constants/strings'; type RegistrationScreenOneRouteProp = RouteProp< OnboardingStackParams, @@ -96,14 +97,9 @@ const RegistrationOne: React.FC<RegistrationOneProps> = ({navigation}) => { screenType: VerificationScreenType.Phone, }); } else if (otpStatusCode === 409) { - Alert.alert( - 'This phone number is already registered with us, please use another email.', - ); + Alert.alert(ERROR_EMAIL_IN_USE); } else { - Alert.alert( - "Looks like Our phone servers might be down 😓'", - "Try again in a couple minutes. We're sorry for the inconvenience.", - ); + Alert.alert(ERROR_SERVER_DOWN); } } else { setForm({...form, attemptedSubmit: false}); diff --git a/src/screens/onboarding/RegistrationThree.tsx b/src/screens/onboarding/RegistrationThree.tsx index 52a6de84..03348e6b 100644 --- a/src/screens/onboarding/RegistrationThree.tsx +++ b/src/screens/onboarding/RegistrationThree.tsx @@ -30,6 +30,11 @@ import { import {passwordRegex, usernameRegex, REGISTER_ENDPOINT} from '../../constants'; import AsyncStorage from '@react-native-community/async-storage'; import {BackgroundGradientType} from '../../types'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_REGISTRATION, + ERROR_SOMETHING_WENT_WRONG_REFRESH, +} from '../../constants/strings'; type RegistrationScreenThreeRouteProp = RouteProp< OnboardingStackParams, @@ -189,12 +194,9 @@ const RegistrationThree: React.FC<RegistrationThreeProps> = ({ console.log(err); } } else if (statusCode === 409) { - Alert.alert('Registration failed 😔', `${data}`); + Alert.alert(ERROR_REGISTRATION(data)); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } else { Alert.alert( @@ -207,10 +209,7 @@ const RegistrationThree: React.FC<RegistrationThreeProps> = ({ setTimeout(() => setForm({...form, attemptedSubmit: true})); } } catch (error) { - Alert.alert( - 'Registration failed 😓', - 'Please double-check your network connection and retry.', - ); + Alert.alert(ERROR_REGISTRATION(ERROR_DOUBLE_CHECK_CONNECTION)); return { name: 'Registration error', description: error, diff --git a/src/screens/onboarding/RegistrationTwo.tsx b/src/screens/onboarding/RegistrationTwo.tsx index 2f67d8c8..707e621a 100644 --- a/src/screens/onboarding/RegistrationTwo.tsx +++ b/src/screens/onboarding/RegistrationTwo.tsx @@ -23,6 +23,7 @@ import { import {nameRegex, emailRegex} from '../../constants'; import {BackgroundGradientType} from '../../types'; +import {ERROR_NEXT_PAGE} from '../../constants/strings'; type RegistrationScreenTwoRouteProp = RouteProp< OnboardingStackParams, @@ -143,10 +144,7 @@ const RegistrationTwo: React.FC<RegistrationTwoProps> = ({ setTimeout(() => setForm({...form, attemptedSubmit: true})); } } catch (error) { - Alert.alert( - 'There was a problem while loading the next page 😓', - "Try again in a couple minutes. We're sorry for the inconvenience.", - ); + Alert.alert(ERROR_NEXT_PAGE); return { name: 'Navigation error', description: error, diff --git a/src/screens/onboarding/Verification.tsx b/src/screens/onboarding/Verification.tsx index c808f30b..0fbe0d91 100644 --- a/src/screens/onboarding/Verification.tsx +++ b/src/screens/onboarding/Verification.tsx @@ -49,6 +49,10 @@ interface VerificationProps { } import {codeRegex} from '../../constants'; +import { + ERROR_INVALID_VERIFICATION_CODE_FORMAT, + ERROR_SOMETHING_WENT_WRONG, +} from '../../constants/strings'; const Verification: React.FC<VerificationProps> = ({route, navigation}) => { const [value, setValue] = React.useState(''); @@ -93,10 +97,10 @@ const Verification: React.FC<VerificationProps> = ({route, navigation}) => { } } catch (error) { console.log(error); - Alert.alert('Something went wrong'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); } } else { - Alert.alert('Please enter a valid 6 digit code'); + Alert.alert(ERROR_INVALID_VERIFICATION_CODE_FORMAT); } }; @@ -115,7 +119,7 @@ const Verification: React.FC<VerificationProps> = ({route, navigation}) => { } } catch (error) { console.log(error); - Alert.alert('Something went wrong'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); } }; diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 5537d6bf..bc85d338 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -1,30 +1,27 @@ +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; import React from 'react'; import { - StyleSheet, - View, Image, - Alert, Keyboard, - TouchableWithoutFeedback, KeyboardAvoidingView, Platform, + StyleSheet, + TouchableWithoutFeedback, + View, } from 'react-native'; import {Button} from 'react-native-elements'; -import {SearchBackground, TaggBigInput} from '../../components'; -import {SCREEN_WIDTH, StatusBarHeight} from '../../utils'; -import AsyncStorage from '@react-native-community/async-storage'; -import {RouteProp} from '@react-navigation/native'; +import {useDispatch, useSelector} from 'react-redux'; import {MainStackParams} from 'src/routes'; -import {StackNavigationProp} from '@react-navigation/stack'; +import {SearchBackground, TaggBigInput} from '../../components'; import {CaptionScreenHeader} from '../../components/'; -import {MOMENTS_ENDPOINT} from '../../constants'; -import {useDispatch, useSelector} from 'react-redux'; +import {postMoment} from '../../services'; import { loadUserMoments, updateProfileCompletionStage, } from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import {postMoment} from '../../services'; +import {SCREEN_WIDTH, StatusBarHeight} from '../../utils'; /** * Upload Screen to allow users to upload posts to Tagg diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx index a6849c7a..3fea14bf 100644 --- a/src/screens/profile/EditProfile.tsx +++ b/src/screens/profile/EditProfile.tsx @@ -40,6 +40,12 @@ import {RootState} from '../../store/rootReducer'; import {useDispatch, useSelector} from 'react-redux'; import {loadUserData} from '../../store/actions'; import {BackgroundGradientType} from '../../types'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_SOMETHING_WENT_WRONG_REFRESH, + ERROR_UPLOAD_LARGE_PROFILE_PIC, + ERROR_UPLOAD_SMALL_PROFILE_PIC, +} from '../../constants/strings'; type EditProfileNavigationProp = StackNavigationProp< ProfileStackParams, @@ -250,11 +256,11 @@ const EditProfile: React.FC<EditProfileProps> = ({route, navigation}) => { const handleSubmit = useCallback(async () => { if (!form.largePic) { - Alert.alert('Please select a Header image!'); + Alert.alert(ERROR_UPLOAD_LARGE_PROFILE_PIC); return; } if (!form.smallPic) { - Alert.alert('Please select a Profile Picture!'); + Alert.alert(ERROR_UPLOAD_SMALL_PROFILE_PIC); return; } if (!form.attemptedSubmit) { @@ -355,13 +361,10 @@ const EditProfile: React.FC<EditProfileProps> = ({route, navigation}) => { data.error || 'Something went wrong! ðŸ˜', ); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } catch (error) { - Alert.alert('Please double-check your network connection and retry.'); + Alert.alert(ERROR_DOUBLE_CHECK_CONNECTION); return { name: 'Profile creation error', description: error, diff --git a/src/services/BlockUserService.ts b/src/services/BlockUserService.ts index 21e259b6..12ea0184 100644 --- a/src/services/BlockUserService.ts +++ b/src/services/BlockUserService.ts @@ -2,6 +2,7 @@ import {Alert} from 'react-native'; import {BLOCK_USER_ENDPOINT} from '../constants'; +import {ERROR_SOMETHING_WENT_WRONG_REFRESH} from '../constants/strings'; export const loadBlockedUsers = async (userId: string, token: string) => { try { @@ -44,18 +45,12 @@ export const blockOrUnblockUser = async ( 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?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); 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?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); return false; } }; @@ -77,18 +72,12 @@ export const isUserBlocked = async ( if (Math.floor(response.status / 100) === 2) { const data = await response.json(); - return data['is_blocked']; + 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?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } catch (error) { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } }; diff --git a/src/services/MomentCategoryService.ts b/src/services/MomentCategoryService.ts index 57e64830..bb2c5542 100644 --- a/src/services/MomentCategoryService.ts +++ b/src/services/MomentCategoryService.ts @@ -1,5 +1,6 @@ import {Alert} from 'react-native'; import {MOMENT_CATEGORY_ENDPOINT} from '../constants'; +import {ERROR_CATEGORY_CREATION} from '../constants/strings'; export const loadMomentCategories: ( userId: string, @@ -44,10 +45,10 @@ export const postMomentCategories: ( const status = response.status; const data = await response.json(); if (status === 200) { - return data['profile_completion_stage']; + return data.profile_completion_stage; } else { - Alert.alert('There was a problem updating categories!'); - console.log('Unable to update categories'); + Alert.alert(ERROR_CATEGORY_CREATION); + console.log('Could not post categories!'); } } catch (err) { console.log(err); diff --git a/src/services/MomentServices.ts b/src/services/MomentServices.ts index 91ecf712..514b674c 100644 --- a/src/services/MomentServices.ts +++ b/src/services/MomentServices.ts @@ -1,6 +1,16 @@ import AsyncStorage from '@react-native-community/async-storage'; import {Alert} from 'react-native'; -import {COMMENTS_ENDPOINT, MOMENTS_ENDPOINT} from '../constants'; +import RNFetchBlob from 'rn-fetch-blob'; +import { + COMMENTS_ENDPOINT, + MOMENTS_ENDPOINT, + MOMENT_THUMBNAIL_ENDPOINT, +} from '../constants'; +import { + ERROR_FAILED_TO_COMMENT, + ERROR_UPLOAD, + SUCCESS_PIC_UPLOAD, +} from '../constants/strings'; import {MomentType} from '../types'; import {checkImageUploadStatus} from '../utils'; @@ -48,20 +58,12 @@ export const postMomentComment = async ( }, body: request, }); - const status = response.status; - if (status === 200) { - const response_data = await response.json(); - return response_data; - } else { - Alert.alert('Something went wrong! ðŸ˜', 'Not able to post a comment'); - return {}; + if (response.status !== 200) { + throw 'server error'; } + return await response.json(); } catch (error) { - Alert.alert( - 'Something went wrong! ðŸ˜', - 'Not able to post a comment', - error, - ); + Alert.alert(ERROR_FAILED_TO_COMMENT); return {}; } }; @@ -136,15 +138,15 @@ export const postMoment: ( }); let statusCode = response.status; let data = await response.json(); - if (statusCode === 200 && checkImageUploadStatus(data['moments'])) { - Alert.alert('The picture was uploaded successfully!'); - return data['profile_completion_stage']; + if (statusCode === 200 && checkImageUploadStatus(data.moments)) { + Alert.alert(SUCCESS_PIC_UPLOAD); + return data.profile_completion_stage; } else { - Alert.alert('An error occured while uploading. Please try again!'); + Alert.alert(ERROR_UPLOAD); } } catch (err) { console.log(err); - Alert.alert('An error occured during authenticaion. Please login again!'); + Alert.alert(ERROR_UPLOAD); } return undefined; }; @@ -193,3 +195,24 @@ export const deleteMoment = async (momentId: string) => { return false; } }; + +export const loadMomentThumbnail = async (momentId: string) => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await RNFetchBlob.config({ + fileCache: true, + appendExt: 'jpg', + }).fetch('GET', MOMENT_THUMBNAIL_ENDPOINT + `${momentId}/`, { + Authorization: 'Token ' + token, + }); + const status = response.info().status; + if (status === 200) { + return response.path(); + } else { + return undefined; + } + } catch (error) { + console.log(error); + return undefined; + } +}; diff --git a/src/services/ReportingService.ts b/src/services/ReportingService.ts index 1563d086..8c0a4bfb 100644 --- a/src/services/ReportingService.ts +++ b/src/services/ReportingService.ts @@ -3,6 +3,10 @@ import {REPORT_ISSUE_ENDPOINT} from '../constants'; import {Alert} from 'react-native'; import AsyncStorage from '@react-native-community/async-storage'; +import { + ERROR_SOMETHING_WENT_WRONG, + MARKED_AS_MSG, +} from '../constants/strings'; export const sendReport = async ( moment_id: string, @@ -25,15 +29,15 @@ export const sendReport = async ( let statusCode = response.status; if (statusCode === 200) { - Alert.alert('Marked as ' + message.split(' ')[2]); + Alert.alert(MARKED_AS_MSG(message.split(' ')[2])); } else { - Alert.alert('Something went wrong!', 'Please try again.'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); } if (callback) { callback(); } } catch (error) { - Alert.alert('Something went wrong!', 'Please try again.'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); console.log( 'Something went wrong! ðŸ˜', 'Unable able to retrieve data', diff --git a/src/services/SocialLinkingService.ts b/src/services/SocialLinkingService.ts index 4a01ee50..1423c8c0 100644 --- a/src/services/SocialLinkingService.ts +++ b/src/services/SocialLinkingService.ts @@ -12,6 +12,8 @@ import { LINK_TWITTER_ENDPOINT, LINK_TWITTER_OAUTH, } from '../constants'; +import {COMING_SOON_MSG, ERROR_LINK, SUCCESS_LINK} from '../constants/strings'; +import {CategorySelection} from '../screens'; // A list of endpoint strings for all the integrated socials export const integratedEndpoints: {[social: string]: [string, string]} = { @@ -124,7 +126,7 @@ export const handlePressForAuthBrowser: ( ) => Promise<boolean> = async (socialType: string) => { try { if (!(socialType in integratedEndpoints)) { - Alert.alert('Coming soon!'); + Alert.alert(COMING_SOON_MSG); return false; } @@ -168,7 +170,7 @@ export const handlePressForAuthBrowser: ( if (!success) { throw 'Unable to register with backend'; } - Alert.alert(`Successfully linked ${socialType} 🎉`); + Alert.alert(SUCCESS_LINK(socialType)); return true; } else if (response.type === 'cancel') { return false; @@ -178,14 +180,12 @@ export const handlePressForAuthBrowser: ( }) .catch((error) => { console.log(error); - Alert.alert( - `Something went wrong, we can't link with ${socialType} 😔`, - ); + Alert.alert(ERROR_LINK(socialType)); return false; }); } catch (error) { console.log(error); - Alert.alert(`Something went wrong, we can't link with ${socialType} 😔`); + Alert.alert(ERROR_LINK(socialType)); } return false; }; diff --git a/src/services/UserFriendsServices.ts b/src/services/UserFriendsServices.ts index 0b138fc3..f2e15824 100644 --- a/src/services/UserFriendsServices.ts +++ b/src/services/UserFriendsServices.ts @@ -1,7 +1,9 @@ //Abstracted common friends api calls out here import {Alert} from 'react-native'; +import {FriendshipStatusType} from 'src/types'; import {FRIENDS_ENDPOINT} from '../constants'; +import {ERROR_SOMETHING_WENT_WRONG_REFRESH} from '../constants/strings'; export const loadFriends = async (userId: string, token: string) => { try { @@ -26,19 +28,76 @@ export const friendOrUnfriendUser = async ( user: string, friend: string, token: string, - isFriend: boolean, + friendship_status: FriendshipStatusType, ) => { try { - const endpoint = FRIENDS_ENDPOINT + (isFriend ? `${user}/` : ''); + let body; + let method = ''; + let endpoint = FRIENDS_ENDPOINT; + + switch (friendship_status) { + case 'no_record': + method = 'POST'; + body = JSON.stringify({ + requested: friend, + }); + break; + case 'requested': + method = 'DELETE'; + endpoint += `${friend}/`; + body = JSON.stringify({ + reason: 'cancelled', + }); + break; + case 'friends': + method = 'DELETE'; + endpoint += `${friend}/`; + body = JSON.stringify({ + reason: 'unfriended', + }); + } + const response = await fetch(endpoint, { - method: isFriend ? 'DELETE' : 'POST', + method: method, headers: { 'Content-Type': 'application/json', Authorization: 'Token ' + token, }, + body: body, + }); + const status = response.status; + if (Math.floor(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 declineFriendRequestService = 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({ - user, - friend, + reason: 'declined', }), }); const status = response.status; @@ -46,6 +105,33 @@ export const friendOrUnfriendUser = async ( 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; + } +}; + +export const acceptFriendRequestService = async ( + requester_id: string, + token: string | null, +) => { + try { + const response = await fetch(FRIENDS_ENDPOINT + `${requester_id}/`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Token ' + token, + }, + }); + const status = response.status; + if (Math.floor(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?", diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index 793ee44d..75d7d367 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -17,6 +17,17 @@ import { SEND_OTP_ENDPOINT, PROFILE_PHOTO_THUMBNAIL_ENDPOINT, } from '../constants'; +import { + ERROR_DOUBLE_CHECK_CONNECTION, + ERROR_DUP_OLD_PWD, + ERROR_INVALID_PWD_CODE, + ERROR_PWD_ACCOUNT, + ERROR_SOMETHING_WENT_WRONG, + ERROR_SOMETHING_WENT_WRONG_REFRESH, + ERROR_VERIFICATION_FAILED_SHORT, + SUCCESS_PWD_RESET, + SUCCESS_VERIFICATION_CODE_SENT, +} from '../constants/strings'; export const loadProfileInfo = async (token: string, userId: string) => { try { @@ -39,6 +50,8 @@ export const loadProfileInfo = async (token: string, userId: string) => { tiktok, university_class, profile_completion_stage, + friendship_status, + friendship_requester_id, } = info; birthday = birthday && moment(birthday).format('YYYY-MM-DD'); return { @@ -51,15 +64,14 @@ export const loadProfileInfo = async (token: string, userId: string) => { tiktok, university_class, profile_completion_stage, + friendship_status, + friendship_requester_id, }; } else { throw 'Unable to load profile data'; } } catch (error) { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } }; @@ -174,10 +186,7 @@ export const handlePasswordResetRequest = async (value: string) => { `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, ); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } console.log(response); @@ -185,7 +194,7 @@ export const handlePasswordResetRequest = async (value: string) => { } } catch (error) { console.log(error); - Alert.alert('Something went wrong! ðŸ˜', 'Looks like our servers are down'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); return false; } }; @@ -211,16 +220,11 @@ export const handlePasswordCodeVerification = async ( return true; } else { if (status == 404) { - Alert.alert( - `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, - ); + Alert.alert(ERROR_PWD_ACCOUNT(TAGG_CUSTOMER_SUPPORT)); } else if (status === 401) { - Alert.alert('Looks like you have entered the wrong code'); + Alert.alert(ERROR_INVALID_PWD_CODE); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); } console.log(response); @@ -228,7 +232,7 @@ export const handlePasswordCodeVerification = async ( } } catch (error) { console.log(error); - Alert.alert('Something went wrong! ðŸ˜', 'Looks like our servers are down'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); return false; } }; @@ -248,27 +252,22 @@ export const handlePasswordReset = async (value: string, password: string) => { }); const status = response.status; if (status === 200) { - Alert.alert('Your password was reset successfully'); + Alert.alert(SUCCESS_PWD_RESET); return true; } else { if (status == 404) { - Alert.alert( - `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, - ); + Alert.alert(ERROR_PWD_ACCOUNT(TAGG_CUSTOMER_SUPPORT)); } else if (status == 406) { - Alert.alert('You may not use an already used password'); + Alert.alert(ERROR_DUP_OLD_PWD); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } console.log(response); return false; } } catch (error) { console.log(error); - Alert.alert('Something went wrong! ðŸ˜', 'Looks like our servers are down'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); return false; } }; @@ -292,17 +291,11 @@ export const verifyOtp = async (phone: string, otp: string) => { 'Try again. Tap the resend code button if you need a new code.', ); } else { - Alert.alert( - 'Something went wrong! ðŸ˜', - "Would you believe me if I told you that I don't know what happened?", - ); + Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH); } } } catch (error) { - Alert.alert( - 'Verifiation failed 😓', - 'Please double-check your network connection and retry.', - ); + Alert.alert(ERROR_VERIFICATION_FAILED_SHORT, ERROR_DOUBLE_CHECK_CONNECTION); return { name: 'Verification error', description: error, @@ -322,13 +315,10 @@ export const sendOtp = async (phone: string) => { let status = response.status; if (status === 200) { - Alert.alert( - 'New verification code sent!', - 'Check your phone messages for your code.', - ); + Alert.alert(SUCCESS_VERIFICATION_CODE_SENT); return true; } else { - Alert.alert('Something went wrong!'); + Alert.alert(ERROR_SOMETHING_WENT_WRONG); return false; } } catch (error) { diff --git a/src/store/actions/userFriends.ts b/src/store/actions/userFriends.ts index 24e32607..18ad247c 100644 --- a/src/store/actions/userFriends.ts +++ b/src/store/actions/userFriends.ts @@ -1,9 +1,24 @@ import {getTokenOrLogout} from '../../utils'; import {RootState} from '../rootReducer'; -import {ProfilePreviewType, UserType} from '../../types/types'; -import {friendOrUnfriendUser, loadFriends} from '../../services'; +import { + FriendshipStatusType, + ProfilePreviewType, + ScreenType, + UserType, +} from '../../types/types'; +import { + acceptFriendRequestService, + declineFriendRequestService, + friendOrUnfriendUser, + loadFriends, +} from '../../services'; import {Action, ThunkAction} from '@reduxjs/toolkit'; -import {userFriendsFetched, updateFriends} from '../reducers'; +import { + userFriendsFetched, + updateFriends, + userXFriendshipEdited, + userLoggedIn, +} from '../reducers'; export const loadFriendsData = ( userId: string, @@ -23,26 +38,67 @@ export const loadFriendsData = ( }; export const friendUnfriendUser = ( - user: UserType, - friend: ProfilePreviewType, - isFriend: boolean, + user: UserType, //logged in user + friend: ProfilePreviewType, // userX's profile preview + friendship_status: FriendshipStatusType, // friendshp status with userx + 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 friendOrUnfriendUser( user.userId, friend.id, token, - isFriend, + friendship_status, ); if (success) { + let data = 'no_record'; + switch (friendship_status) { + case 'no_record': // send request: update to requested + data = 'requested'; + break; + case 'requested': // cancel request: update to no relationship + case 'friends': // unfriend: update to no relationship + dispatch({ + type: updateFriends.type, + payload: { + friend, + isFriend: true, + }, + }); + data = 'no_record'; + } + 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 ( + dispatch, +) => { + try { + const token = await getTokenOrLogout(dispatch); + const success = await acceptFriendRequestService(requester.id, token); + if (success) { dispatch({ type: updateFriends.type, payload: { - isFriend, - data: friend, + data: requester, + isFriend: false, }, }); } @@ -50,3 +106,28 @@ export const friendUnfriendUser = ( console.log(error); } }; + +export const declineFriendRequest = ( + user_id: string, +): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async ( + dispatch, +) => { + try { + const token = await getTokenOrLogout(dispatch); + const success = await declineFriendRequestService(user_id, token); + if (success) { + // Get profile of requester + console.log('declined request: ', success); + // dispatch({ + // type: updateFriends.type, + // payload: { + // data: requester, // has to be a requester not id + // }, + // }); + } else { + console.log('Unsuccessful call'); + } + } catch (error) { + console.log(error); + } +}; diff --git a/src/store/actions/userX.ts b/src/store/actions/userX.ts index 0f87012d..07bea678 100644 --- a/src/store/actions/userX.ts +++ b/src/store/actions/userX.ts @@ -38,12 +38,12 @@ export const loadUserX = ( payload: {screenType, userId, user}, }); const token = await getTokenOrLogout(dispatch); - loadProfileInfo(token, userId).then((data) => + loadProfileInfo(token, userId).then((data) => { dispatch({ type: userXProfileFetched.type, payload: {screenType, userId, data}, - }), - ); + }); + }); loadAllSocialsForUser(userId).then((data) => dispatch({ type: userXSocialsFetched.type, @@ -92,7 +92,11 @@ export const updateUserXFriends = ( dispatch, ) => { try { - const screens = <ScreenType[]>[ScreenType.Profile, ScreenType.Search]; + const screens = <ScreenType[]>[ + ScreenType.Profile, + ScreenType.Search, + ScreenType.Notifications, + ]; const token = await getTokenOrLogout(dispatch); screens.forEach((screenType) => { if (userXInStore(state, screenType, userId)) { @@ -123,3 +127,31 @@ export const resetScreenType = ( console.log(error); } }; + +export const updateUserXProfileAllScreens = ( + userId: string, + state: RootState, +): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async ( + dispatch, +) => { + try { + const screens = <ScreenType[]>[ + ScreenType.Profile, + ScreenType.Search, + ScreenType.Notifications, + ]; + const token = await getTokenOrLogout(dispatch); + screens.forEach((screenType) => { + if (userXInStore(state, screenType, userId)) { + loadProfileInfo(token, userId).then((data) => { + dispatch({ + type: userXProfileFetched.type, + payload: {screenType, userId, data}, + }); + }); + } + }); + } catch (error) { + console.log(error); + } +}; diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index 87e1ce22..08dc7077 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -20,6 +20,8 @@ export const NO_PROFILE: ProfileType = { profile_completion_stage: 1, snapchat: '', tiktok: '', + friendship_status: 'no_record', + friendship_requester_id: '', }; export const EMPTY_MOMENTS_LIST = <MomentType[]>[]; diff --git a/src/store/reducers/userXReducer.ts b/src/store/reducers/userXReducer.ts index 3b00cf88..9f90d58d 100644 --- a/src/store/reducers/userXReducer.ts +++ b/src/store/reducers/userXReducer.ts @@ -60,6 +60,12 @@ const userXSlice = createSlice({ ].socialAccounts = action.payload.data; }, + userXFriendshipEdited: (state, action) => { + state[<ScreenType>action.payload.screenType][ + action.payload.userId + ].profile.friendship_status = action.payload.data; + }, + resetScreen: (state, action) => { for (let userId in state[<ScreenType>action.payload.screenType]) { state[<ScreenType>action.payload.screenType][userId] = EMPTY_USER_X; @@ -78,6 +84,7 @@ export const { userXProfileFetched, userXSocialsFetched, userXMomentCategoriesFetched, + userXFriendshipEdited, resetScreen, } = userXSlice.actions; export const userXReducer = userXSlice.reducer; diff --git a/src/types/types.ts b/src/types/types.ts index 093adbe4..d9d0b56b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -13,6 +13,8 @@ export interface ProfilePreviewType { last_name: string; } +export type FriendshipStatusType = 'friends' | 'requested' | 'no_record'; + export interface ProfileType { name: string; biography: string; @@ -23,6 +25,8 @@ export interface ProfileType { birthday: Date | undefined; snapchat: string; tiktok: string; + friendship_status: FriendshipStatusType; + friendship_requester_id: string; } export interface SocialAccountType { @@ -165,7 +169,7 @@ export type TaggPopupType = { export type NotificationType = { actor: ProfilePreviewType; verbage: string; - notification_type: 'DFT' | 'FRD' | 'CMT'; + notification_type: 'DFT' | 'FRD_REQ' | 'FRD_ACPT' | 'FRD_DEC' | 'CMT'; notification_object: CommentType | undefined; timestamp: string; unread: boolean; diff --git a/src/utils/common.ts b/src/utils/common.ts index dbe8f270..6314cc13 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,5 +1,5 @@ import moment from 'moment'; -import {Linking} from 'react-native'; +import {AsyncStorage, Linking} from 'react-native'; import {BROWSABLE_SOCIAL_URLS, TOGGLE_BUTTON_TYPE} from '../constants'; export const getToggleButtonText: ( diff --git a/src/utils/users.ts b/src/utils/users.ts index c54ea715..ca917ae4 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,6 +1,6 @@ import AsyncStorage from '@react-native-community/async-storage'; import {INTEGRATED_SOCIAL_LIST} from '../constants'; -import {loadSocialPosts} from '../services'; +import {isUserBlocked, loadSocialPosts} from '../services'; import { loadAllSocials, loadBlockedList, @@ -9,6 +9,7 @@ import { loadUserData, loadUserMoments, loadUserNotifications, + logout, } from '../store/actions'; import {NO_SOCIAL_ACCOUNTS} from '../store/initialStates'; import {userLoggedIn} from '../store/reducers'; @@ -16,7 +17,12 @@ import {loadUserMomentCategories} from './../store/actions/momentCategories'; import {loadUserX} from './../store/actions/userX'; import {AppDispatch} from './../store/configureStore'; import {RootState} from './../store/rootReducer'; -import {ScreenType, UserType} from './../types/types'; +import { + ProfilePreviewType, + ProfileType, + ScreenType, + UserType, +} from './../types/types'; const loadData = async (dispatch: AppDispatch, user: UserType) => { await Promise.all([ @@ -122,3 +128,34 @@ export const getTokenOrLogout = async (dispatch: Function): Promise<string> => { } return token; }; + +/** + * Creates ProfilePreviewType of a user using UserType && ProfileType + * @param passedInUser This is the UserType of the user + * @param passedInProfile This is the ProfileType of the user + */ +export 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], + }; +}; + +export const checkIfUserIsBlocked = async ( + userId: string, + dispatch: Function, + loggedInUser: UserType, +) => { + const token = await AsyncStorage.getItem('token'); + if (!token) { + dispatch(logout()); + return false; + } + return await isUserBlocked(userId, loggedInUser.userId, token); +}; |