aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/components/common/AcceptDeclineButtons.tsx78
-rw-r--r--src/components/common/TaggPopup.tsx71
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/moments/Moment.tsx24
-rw-r--r--src/components/notifications/Notification.tsx150
-rw-r--r--src/components/onboarding/LinkSocialMedia.tsx5
-rw-r--r--src/components/profile/Content.tsx62
-rw-r--r--src/components/profile/MomentMoreInfoDrawer.tsx7
-rw-r--r--src/components/profile/ProfileBody.tsx143
-rw-r--r--src/components/profile/ProfilePreview.tsx28
-rw-r--r--src/components/taggs/Tagg.tsx17
-rw-r--r--src/constants/api.ts1
-rw-r--r--src/constants/strings.ts47
-rw-r--r--src/routes/onboarding/OnboardingStackScreen.tsx8
-rw-r--r--src/screens/main/NotificationsScreen.tsx13
-rw-r--r--src/screens/onboarding/CategorySelection.tsx5
-rw-r--r--src/screens/onboarding/InvitationCodeVerification.tsx17
-rw-r--r--src/screens/onboarding/Login.tsx50
-rw-r--r--src/screens/onboarding/ProfileOnboarding.tsx24
-rw-r--r--src/screens/onboarding/RegistrationOne.tsx10
-rw-r--r--src/screens/onboarding/RegistrationThree.tsx17
-rw-r--r--src/screens/onboarding/RegistrationTwo.tsx6
-rw-r--r--src/screens/onboarding/Verification.tsx10
-rw-r--r--src/screens/profile/CaptionScreen.tsx21
-rw-r--r--src/screens/profile/EditProfile.tsx17
-rw-r--r--src/services/BlockUserService.ts23
-rw-r--r--src/services/MomentCategoryService.ts7
-rw-r--r--src/services/MomentServices.ts59
-rw-r--r--src/services/ReportingService.ts10
-rw-r--r--src/services/SocialLinkingService.ts12
-rw-r--r--src/services/UserFriendsServices.ts96
-rw-r--r--src/services/UserProfileService.ts72
-rw-r--r--src/store/actions/userFriends.ts99
-rw-r--r--src/store/actions/userX.ts40
-rw-r--r--src/store/initialStates.ts2
-rw-r--r--src/store/reducers/userXReducer.ts7
-rw-r--r--src/types/types.ts6
-rw-r--r--src/utils/common.ts2
-rw-r--r--src/utils/users.ts41
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);
+};