aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/images/private-profile.pngbin0 -> 3750 bytes
-rw-r--r--src/assets/images/private-profile@2x.pngbin0 -> 7999 bytes
-rw-r--r--src/assets/images/private-profile@3x.pngbin0 -> 10081 bytes
-rw-r--r--src/components/common/TaggPrompt.tsx2
-rw-r--r--src/components/profile/Content.tsx292
-rw-r--r--src/components/profile/FriendsCount.tsx21
-rw-r--r--src/components/profile/PrivateProfile.tsx34
-rw-r--r--src/components/profile/ProfileBody.tsx19
-rw-r--r--src/components/profile/PublicProfile.tsx280
-rw-r--r--src/components/taggs/Tagg.tsx5
-rw-r--r--src/components/taggs/TaggsBar.tsx14
-rw-r--r--src/constants/strings.ts1
-rw-r--r--src/screens/suggestedPeople/SPBody.tsx11
-rw-r--r--src/services/UserFriendsService.ts9
-rw-r--r--src/store/actions/userFriends.ts23
-rw-r--r--src/types/types.ts8
-rw-r--r--src/utils/users.ts32
17 files changed, 433 insertions, 318 deletions
diff --git a/src/assets/images/private-profile.png b/src/assets/images/private-profile.png
new file mode 100644
index 00000000..820ba287
--- /dev/null
+++ b/src/assets/images/private-profile.png
Binary files differ
diff --git a/src/assets/images/private-profile@2x.png b/src/assets/images/private-profile@2x.png
new file mode 100644
index 00000000..cdcf44d5
--- /dev/null
+++ b/src/assets/images/private-profile@2x.png
Binary files differ
diff --git a/src/assets/images/private-profile@3x.png b/src/assets/images/private-profile@3x.png
new file mode 100644
index 00000000..f909fe2a
--- /dev/null
+++ b/src/assets/images/private-profile@3x.png
Binary files differ
diff --git a/src/components/common/TaggPrompt.tsx b/src/components/common/TaggPrompt.tsx
index d65e30c6..6169b3f1 100644
--- a/src/components/common/TaggPrompt.tsx
+++ b/src/components/common/TaggPrompt.tsx
@@ -66,7 +66,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
- height: SCREEN_HEIGHT / 4.5,
+ height: SCREEN_HEIGHT / 4,
},
closeButton: {
position: 'relative',
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 1a5a205c..a22b9728 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,128 +1,74 @@
-import {useFocusEffect, useNavigation} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {
- Alert,
LayoutChangeEvent,
NativeScrollEvent,
NativeSyntheticEvent,
RefreshControl,
StyleSheet,
- Text,
- View,
} from 'react-native';
-import {TouchableOpacity} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import {useDispatch, useSelector, useStore} from 'react-redux';
-import Cover from './Cover';
-import GreyPlusLogo from '../../assets/icons/grey-plus-logo.svg';
-import {COVER_HEIGHT, TAGG_LIGHT_BLUE} from '../../constants';
-import {
- UPLOAD_MOMENT_PROMPT_THREE_HEADER,
- UPLOAD_MOMENT_PROMPT_THREE_MESSAGE,
- UPLOAD_MOMENT_PROMPT_TWO_HEADER,
- UPLOAD_MOMENT_PROMPT_TWO_MESSAGE,
-} from '../../constants/strings';
+import {COVER_HEIGHT} from '../../constants';
import {
blockUnblockUser,
- deleteUserMomentsForCategory,
loadFriendsData,
- updateMomentCategories,
updateUserXFriends,
} from '../../store/actions';
import {
- EMPTY_MOMENTS_LIST,
EMPTY_PROFILE_PREVIEW_LIST,
NO_PROFILE,
NO_USER,
} from '../../store/initialStates';
import {RootState} from '../../store/rootreducer';
-import {CategorySelectionScreenType, MomentType, ScreenType} from '../../types';
+import {ContentProps} from '../../types';
import {
+ canViewProfile,
fetchUserX,
getUserAsProfilePreviewType,
- moveCategory,
- normalize,
SCREEN_HEIGHT,
userLogin,
} from '../../utils';
-import {TaggPrompt} from '../common';
-import {Moment} from '../moments';
-import TaggsBar from '../taggs/TaggsBar';
+import Cover from './Cover';
+import PrivateProfile from './PrivateProfile';
import ProfileBody from './ProfileBody';
import ProfileCutout from './ProfileCutout';
import ProfileHeader from './ProfileHeader';
-
-interface ContentProps {
- y: Animated.Value<number>;
- userXId: string | undefined;
- screenType: ScreenType;
-}
+import PublicProfile from './PublicProfile';
+import TaggsBar from '../taggs/TaggsBar';
const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
const dispatch = useDispatch();
-
- const {user = NO_USER, profile = NO_PROFILE} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.user);
-
- const {friends = EMPTY_PROFILE_PREVIEW_LIST} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.friends);
-
+ const state: RootState = useStore().getState();
const {
- friends: friendsLoggedInUser = EMPTY_PROFILE_PREVIEW_LIST,
- } = useSelector((state: RootState) => state.friends);
-
- const {moments = EMPTY_MOMENTS_LIST} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.moments);
-
- const {momentCategories = []} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.momentCategories);
-
+ user = NO_USER,
+ profile = NO_PROFILE,
+ } = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.user,
+ );
const {blockedUsers = EMPTY_PROFILE_PREVIEW_LIST} = useSelector(
(state: RootState) => state.blocked,
);
const {user: loggedInUser = NO_USER} = useSelector(
(state: RootState) => state.user,
);
- const state = useStore().getState();
-
- const navigation = useNavigation();
/**
* States
*/
- const [imagesMap, setImagesMap] = useState<Map<string, MomentType[]>>(
- new Map(),
- );
- const [isFriend, setIsFriend] = useState<boolean>(false);
const [isBlocked, setIsBlocked] = useState<boolean>(false);
const [profileBodyHeight, setProfileBodyHeight] = useState(0);
const [shouldBounce, setShouldBounce] = useState<boolean>(true);
const [refreshing, setRefreshing] = useState<boolean>(false);
- const [isStageTwoPromptClosed, setIsStageTwoPromptClosed] = useState<boolean>(
- false,
- );
- const [isStageOnePromptClosed, setIsStageOnePromptClosed] = useState<boolean>(
- false,
- );
- const [
- isStageThreePromptClosed,
- setIsStageThreePromptClosed,
- ] = useState<boolean>(false);
-
const onRefresh = useCallback(() => {
const refrestState = async () => {
+ setRefreshing(true);
if (!userXId) {
await userLogin(dispatch, loggedInUser);
} else {
await fetchUserX(dispatch, user, screenType);
}
};
- setRefreshing(true);
refrestState().then(() => {
setRefreshing(false);
});
@@ -133,83 +79,11 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
setProfileBodyHeight(height);
};
- const createImagesMap = useCallback(() => {
- var map = new Map();
- moments.forEach(function (imageObject) {
- var moment_category = imageObject.moment_category;
- if (map.has(moment_category)) {
- map.get(moment_category).push(imageObject);
- } else {
- map.set(moment_category, [imageObject]);
- }
- });
- setImagesMap(map);
- }, [moments]);
-
- useEffect(() => {
- createImagesMap();
- }, [createImagesMap]);
-
- const move = (direction: 'up' | 'down', title: string) => {
- let categories = [...momentCategories];
- categories = moveCategory(categories, title, direction === 'up');
- dispatch(updateMomentCategories(categories, false));
- };
-
- /**
- * Prompt user to perform an activity based on their profile completion stage
- * To fire 2 seconds after the screen comes in focus
- * 1 means STAGE_1:
- * The user must upload a moment, so take them to a screen guiding them to post a moment
- * 2 means STAGE_2:
- * The user must create another category so show a prompt on top of the screen
- * 3 means STAGE_3:
- * The user must upload a moment to the second category, so show a prompt on top of the screen
- * Else, profile is complete and no prompt needs to be shown
- */
- useFocusEffect(
- useCallback(() => {
- const navigateToMomentUploadPrompt = () => {
- switch (profile.profile_completion_stage) {
- case 1:
- if (
- momentCategories &&
- momentCategories[0] &&
- !isStageOnePromptClosed
- ) {
- navigation.navigate('MomentUploadPrompt', {
- screenType,
- momentCategory: momentCategories[0],
- });
- setIsStageOnePromptClosed(true);
- }
- break;
- case 2:
- setIsStageTwoPromptClosed(false);
- break;
- case 3:
- setIsStageThreePromptClosed(false);
- break;
- default:
- break;
- }
- };
- if (!userXId) {
- setTimeout(navigateToMomentUploadPrompt, 2000);
- }
- }, [
- profile.profile_completion_stage,
- momentCategories,
- userXId,
- isStageOnePromptClosed,
- ]),
- );
-
useEffect(() => {
const isActuallyBlocked = blockedUsers.some(
(cur_user) => user.username === cur_user.username,
);
- if (isBlocked != isActuallyBlocked) {
+ if (isBlocked !== isActuallyBlocked) {
setIsBlocked(isActuallyBlocked);
}
}, [blockedUsers, user]);
@@ -234,37 +108,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
}
};
- /**
- * Handle deletion of a category
- * Confirm with user before deleting the category
- * @param category category to be deleted
- */
- const handleCategoryDeletion = (category: string) => {
- Alert.alert(
- 'Category Deletion',
- `Are you sure that you want to delete the category ${category} ?`,
- [
- {
- text: 'Cancel',
- style: 'cancel',
- },
- {
- text: 'Yes',
- onPress: () => {
- dispatch(
- updateMomentCategories(
- momentCategories.filter((mc) => mc !== category),
- false,
- ),
- );
- dispatch(deleteUserMomentsForCategory(category));
- },
- },
- ],
- {cancelable: true},
- );
- };
-
const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
/**
* Set the new y position
@@ -285,6 +128,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
return (
<Animated.ScrollView
+ contentContainerStyle={styles.contentContainer}
style={styles.container}
onScroll={(e) => handleScroll(e)}
bounces={shouldBounce}
@@ -304,7 +148,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
onLayout,
userXId,
screenType,
- isFriend,
handleBlockUnblock,
isBlocked,
}}
@@ -313,111 +156,22 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
{...{y, profileBodyHeight, userXId, screenType}}
whiteRing={undefined}
/>
- <View style={styles.momentsContainer}>
- {userXId && moments.length === 0 && (
- <View style={styles.plusIconContainer}>
- <GreyPlusLogo width={90} height={90} />
- <Text style={styles.noMomentsText}>{`Looks like ${
- profile.name.split(' ')[0]
- } has not posted any moments yet`}</Text>
- </View>
- )}
- {!userXId &&
- profile.profile_completion_stage === 2 &&
- !isStageTwoPromptClosed && (
- <TaggPrompt
- messageHeader={UPLOAD_MOMENT_PROMPT_TWO_HEADER}
- messageBody={UPLOAD_MOMENT_PROMPT_TWO_MESSAGE}
- logoType=""
- onClose={() => {
- setIsStageTwoPromptClosed(true);
- }}
- />
- )}
- {!userXId &&
- profile.profile_completion_stage === 3 &&
- !isStageThreePromptClosed && (
- <TaggPrompt
- messageHeader={UPLOAD_MOMENT_PROMPT_THREE_HEADER}
- messageBody={UPLOAD_MOMENT_PROMPT_THREE_MESSAGE}
- logoType=""
- onClose={() => {
- setIsStageThreePromptClosed(true);
- }}
- />
- )}
- {momentCategories.map(
- (title, index) =>
- (!userXId || imagesMap.get(title)) && (
- <Moment
- key={index}
- title={title}
- images={imagesMap.get(title)}
- userXId={userXId}
- screenType={screenType}
- handleMomentCategoryDelete={handleCategoryDeletion}
- shouldAllowDeletion={momentCategories.length > 1}
- showUpButton={index !== 0}
- showDownButton={index !== momentCategories.length - 1}
- move={move}
- />
- ),
- )}
- {!userXId && (
- <TouchableOpacity
- onPress={() =>
- navigation.push('CategorySelection', {
- screenType: CategorySelectionScreenType.Profile,
- user: loggedInUser,
- })
- }
- style={styles.createCategoryButton}>
- <Text style={styles.createCategoryButtonLabel}>
- Create a new category
- </Text>
- </TouchableOpacity>
- )}
- </View>
+ {canViewProfile(state, userXId, screenType) ? (
+ <PublicProfile {...{y, userXId, screenType}} />
+ ) : (
+ <PrivateProfile />
+ )}
</Animated.ScrollView>
);
};
const styles = StyleSheet.create({
container: {
- flex: 1,
backgroundColor: '#fff',
- },
- momentsContainer: {
- backgroundColor: '#f2f2f2',
- paddingBottom: SCREEN_HEIGHT / 10,
flex: 1,
- flexDirection: 'column',
- },
- createCategoryButton: {
- backgroundColor: TAGG_LIGHT_BLUE,
- justifyContent: 'center',
- alignItems: 'center',
- width: '70%',
- height: 30,
- marginTop: '15%',
- alignSelf: 'center',
- },
- createCategoryButtonLabel: {
- fontSize: normalize(16),
- fontWeight: '500',
- color: 'white',
- },
- plusIconContainer: {
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center',
- marginVertical: '10%',
},
- noMomentsText: {
- fontSize: normalize(14),
- fontWeight: 'bold',
- color: 'gray',
- marginVertical: '8%',
+ contentContainer: {
+ flexGrow: 1,
},
});
diff --git a/src/components/profile/FriendsCount.tsx b/src/components/profile/FriendsCount.tsx
index 851dbc3b..4790743b 100644
--- a/src/components/profile/FriendsCount.tsx
+++ b/src/components/profile/FriendsCount.tsx
@@ -1,11 +1,11 @@
+import {useNavigation} from '@react-navigation/native';
import React from 'react';
-import {View, Text, StyleSheet, ViewProps} from 'react-native';
+import {StyleSheet, Text, View, ViewProps} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
-import {useNavigation} from '@react-navigation/native';
+import {useSelector, useStore} from 'react-redux';
import {RootState} from '../../store/rootReducer';
-import {useSelector} from 'react-redux';
import {ScreenType} from '../../types';
-import {normalize} from '../../utils';
+import {canViewProfile, normalize} from '../../utils';
interface FriendsCountProps extends ViewProps {
userXId: string | undefined;
@@ -17,9 +17,9 @@ const FriendsCount: React.FC<FriendsCountProps> = ({
userXId,
screenType,
}) => {
- const {friends} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.friends);
+ const {friends} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.friends,
+ );
const count = friends ? friends.length : 0;
const displayedCount: string =
@@ -32,15 +32,18 @@ const FriendsCount: React.FC<FriendsCountProps> = ({
: `${count / 1e6}m`;
const navigation = useNavigation();
+ const state: RootState = useStore().getState();
return (
<TouchableOpacity
- style={{right: '20%'}}
onPress={() =>
- navigation.push('FriendsListScreen', {
+ navigation.navigate('FriendsListScreen', {
userXId,
screenType,
})
+ }
+ disabled={
+ !canViewProfile(state, userXId, screenType) || friends.length === 0
}>
<View style={[styles.container, style]}>
<Text style={styles.count}>{displayedCount}</Text>
diff --git a/src/components/profile/PrivateProfile.tsx b/src/components/profile/PrivateProfile.tsx
new file mode 100644
index 00000000..bc75a18a
--- /dev/null
+++ b/src/components/profile/PrivateProfile.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import {Image, StyleSheet, Text, View} from 'react-native';
+import {PRIVATE_ACCOUNT} from '../../constants/strings';
+import {normalize, SCREEN_HEIGHT} from '../../utils';
+
+const PrivateProfile: React.FC = () => {
+ return (
+ <View style={styles.container}>
+ <Image source={require('../../assets/images/private-profile.png')} />
+ <View style={styles.privateAccountTextContainer}>
+ <Text style={styles.privateAccountTextStyle}>{PRIVATE_ACCOUNT}</Text>
+ </View>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: '#F9F9F9',
+ height: SCREEN_HEIGHT * 0.4,
+ paddingBottom: SCREEN_HEIGHT * 0.1,
+ },
+ privateAccountTextContainer: {marginTop: '8%'},
+ privateAccountTextStyle: {
+ fontWeight: '600',
+ fontSize: normalize(18),
+ lineHeight: normalize(25),
+ color: '#828282',
+ },
+});
+
+export default PrivateProfile;
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index 646be3e0..b49e71a3 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -2,21 +2,17 @@ import React from 'react';
import {LayoutChangeEvent, Linking, StyleSheet, Text, View} from 'react-native';
import {normalize} from 'react-native-elements';
import {useDispatch, useSelector, useStore} from 'react-redux';
-import {
- TAGG_DARK_BLUE,
- TAGG_LIGHT_BLUE,
- TOGGLE_BUTTON_TYPE,
-} from '../../constants';
+import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants';
import {
acceptFriendRequest,
declineFriendRequest,
updateUserXFriends,
updateUserXProfileAllScreens,
} from '../../store/actions';
-import {NO_PROFILE, NO_USER} from '../../store/initialStates';
+import {NO_PROFILE} from '../../store/initialStates';
import {RootState} from '../../store/rootReducer';
import {ScreenType} from '../../types';
-import {getUserAsProfilePreviewType, SCREEN_WIDTH} from '../../utils';
+import {getUserAsProfilePreviewType} from '../../utils';
import {FriendsButton} from '../common';
import ToggleButton from './ToggleButton';
@@ -34,12 +30,8 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
userXId,
screenType,
}) => {
- const {profile = NO_PROFILE, user} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.user);
-
- const {user: loggedInUser = NO_USER} = useSelector(
- (state: RootState) => state.user,
+ const {profile = NO_PROFILE, user} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.user,
);
const {
@@ -119,6 +111,7 @@ const styles = StyleSheet.create({
flex: 1,
paddingTop: '3.5%',
paddingBottom: '2%',
+ width: '50%',
},
container: {
paddingVertical: '1%',
diff --git a/src/components/profile/PublicProfile.tsx b/src/components/profile/PublicProfile.tsx
new file mode 100644
index 00000000..9683d8f2
--- /dev/null
+++ b/src/components/profile/PublicProfile.tsx
@@ -0,0 +1,280 @@
+import {useFocusEffect, useNavigation} from '@react-navigation/native';
+import React, {useCallback, useEffect, useState} from 'react';
+import {Alert, StyleSheet, Text, View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {useDispatch, useSelector} from 'react-redux';
+import GreyPlusLogo from '../../assets/icons/grey-plus-logo.svg';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {
+ UPLOAD_MOMENT_PROMPT_THREE_HEADER,
+ UPLOAD_MOMENT_PROMPT_THREE_MESSAGE,
+ UPLOAD_MOMENT_PROMPT_TWO_HEADER,
+ UPLOAD_MOMENT_PROMPT_TWO_MESSAGE,
+} from '../../constants/strings';
+import {
+ deleteUserMomentsForCategory,
+ updateMomentCategories,
+} from '../../store/actions';
+import {
+ EMPTY_MOMENTS_LIST,
+ NO_PROFILE,
+ NO_USER,
+} from '../../store/initialStates';
+import {RootState} from '../../store/rootreducer';
+import {
+ CategorySelectionScreenType,
+ ContentProps,
+ MomentType,
+} from '../../types';
+import {moveCategory, normalize, SCREEN_HEIGHT} from '../../utils';
+import {TaggPrompt} from '../common';
+import {Moment} from '../moments';
+
+const PublicProfile: React.FC<ContentProps> = ({userXId, screenType}) => {
+ const dispatch = useDispatch();
+
+ const {profile = NO_PROFILE} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.user,
+ );
+
+ const {moments = EMPTY_MOMENTS_LIST} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.moments,
+ );
+
+ const {momentCategories = []} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.momentCategories,
+ );
+
+ const {user: loggedInUser = NO_USER} = useSelector(
+ (state: RootState) => state.user,
+ );
+
+ const navigation = useNavigation();
+
+ /**
+ * States
+ */
+ const [imagesMap, setImagesMap] = useState<Map<string, MomentType[]>>(
+ new Map(),
+ );
+
+ const [isStageTwoPromptClosed, setIsStageTwoPromptClosed] = useState(false);
+ const [isStageOnePromptClosed, setIsStageOnePromptClosed] = useState(false);
+ const [isStageThreePromptClosed, setIsStageThreePromptClosed] = useState(
+ false,
+ );
+
+ const move = (direction: 'up' | 'down', title: string) => {
+ let categories = [...momentCategories];
+ categories = moveCategory(categories, title, direction === 'up');
+ dispatch(updateMomentCategories(categories, false));
+ };
+
+ /**
+ * Prompt user to perform an activity based on their profile completion stage
+ * To fire 2 seconds after the screen comes in focus
+ * 1 means STAGE_1:
+ * The user must upload a moment, so take them to a screen guiding them to post a moment
+ * 2 means STAGE_2:
+ * The user must create another category so show a prompt on top of the screen
+ * 3 means STAGE_3:
+ * The user must upload a moment to the second category, so show a prompt on top of the screen
+ * Else, profile is complete and no prompt needs to be shown
+ */
+ useFocusEffect(
+ useCallback(() => {
+ const navigateToMomentUploadPrompt = () => {
+ switch (profile.profile_completion_stage) {
+ case 1:
+ if (
+ momentCategories &&
+ momentCategories[0] &&
+ !isStageOnePromptClosed
+ ) {
+ navigation.navigate('MomentUploadPrompt', {
+ screenType,
+ momentCategory: momentCategories[0],
+ });
+ setIsStageOnePromptClosed(true);
+ }
+ break;
+ case 2:
+ setIsStageTwoPromptClosed(false);
+ break;
+ case 3:
+ setIsStageThreePromptClosed(false);
+ break;
+ default:
+ break;
+ }
+ };
+ if (!userXId) {
+ setTimeout(navigateToMomentUploadPrompt, 2000);
+ }
+ }, [
+ userXId,
+ profile.profile_completion_stage,
+ momentCategories,
+ isStageOnePromptClosed,
+ navigation,
+ screenType,
+ ]),
+ );
+
+ /**
+ * Handle deletion of a category
+ * Confirm with user before deleting the category
+ * @param category category to be deleted
+ */
+ const handleCategoryDeletion = (category: string) => {
+ Alert.alert(
+ 'Category Deletion',
+ `Are you sure that you want to delete the category ${category} ?`,
+ [
+ {
+ text: 'Cancel',
+ style: 'cancel',
+ },
+ {
+ text: 'Yes',
+ onPress: () => {
+ dispatch(
+ updateMomentCategories(
+ momentCategories.filter((mc) => mc !== category),
+ false,
+ ),
+ );
+ dispatch(deleteUserMomentsForCategory(category));
+ },
+ },
+ ],
+ {cancelable: true},
+ );
+ };
+
+ const createImagesMap = useCallback(() => {
+ let map = new Map();
+ moments.forEach(function (imageObject) {
+ let moment_category = imageObject.moment_category;
+ if (map.has(moment_category)) {
+ map.get(moment_category).push(imageObject);
+ } else {
+ map.set(moment_category, [imageObject]);
+ }
+ });
+ setImagesMap(map);
+ }, [moments]);
+
+ useEffect(() => {
+ createImagesMap();
+ }, [createImagesMap]);
+
+ return (
+ <View style={styles.momentsContainer}>
+ {userXId && moments.length === 0 && (
+ <View style={styles.plusIconContainer}>
+ <GreyPlusLogo width={90} height={90} />
+ <Text style={styles.noMomentsText}>{`Looks like ${
+ profile.name.split(' ')[0]
+ } has not posted any moments yet`}</Text>
+ </View>
+ )}
+ {!userXId &&
+ profile.profile_completion_stage === 2 &&
+ !isStageTwoPromptClosed && (
+ <TaggPrompt
+ messageHeader={UPLOAD_MOMENT_PROMPT_TWO_HEADER}
+ messageBody={UPLOAD_MOMENT_PROMPT_TWO_MESSAGE}
+ logoType="tagg"
+ onClose={() => {
+ setIsStageTwoPromptClosed(true);
+ }}
+ />
+ )}
+ {!userXId &&
+ profile.profile_completion_stage === 3 &&
+ !isStageThreePromptClosed && (
+ <TaggPrompt
+ messageHeader={UPLOAD_MOMENT_PROMPT_THREE_HEADER}
+ messageBody={UPLOAD_MOMENT_PROMPT_THREE_MESSAGE}
+ logoType="tagg"
+ onClose={() => {
+ setIsStageThreePromptClosed(true);
+ }}
+ />
+ )}
+ {momentCategories.map(
+ (title, index) =>
+ (!userXId || imagesMap.get(title)) && (
+ <Moment
+ key={index}
+ title={title}
+ images={imagesMap.get(title)}
+ userXId={userXId}
+ screenType={screenType}
+ handleMomentCategoryDelete={handleCategoryDeletion}
+ shouldAllowDeletion={momentCategories.length > 1}
+ showUpButton={index !== 0}
+ showDownButton={index !== momentCategories.length - 1}
+ move={move}
+ />
+ ),
+ )}
+ {!userXId && (
+ <TouchableOpacity
+ onPress={() =>
+ navigation.navigate('CategorySelection', {
+ screenType: CategorySelectionScreenType.Profile,
+ user: loggedInUser,
+ })
+ }
+ style={styles.createCategoryButton}>
+ <Text style={styles.createCategoryButtonLabel}>
+ Create a new category
+ </Text>
+ </TouchableOpacity>
+ )}
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ },
+ momentsContainer: {
+ backgroundColor: '#f2f2f2',
+ paddingBottom: SCREEN_HEIGHT * 0.15,
+ flex: 1,
+ flexDirection: 'column',
+ },
+ createCategoryButton: {
+ backgroundColor: TAGG_LIGHT_BLUE,
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '70%',
+ height: 30,
+ marginTop: '15%',
+ alignSelf: 'center',
+ },
+ createCategoryButtonLabel: {
+ fontSize: normalize(16),
+ fontWeight: '500',
+ color: 'white',
+ },
+ plusIconContainer: {
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginVertical: '10%',
+ },
+ noMomentsText: {
+ fontSize: normalize(14),
+ fontWeight: 'bold',
+ color: 'gray',
+ marginVertical: '8%',
+ },
+});
+
+export default PublicProfile;
diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx
index 547a77cb..4e4987fb 100644
--- a/src/components/taggs/Tagg.tsx
+++ b/src/components/taggs/Tagg.tsx
@@ -34,6 +34,7 @@ interface TaggProps {
userXId: string | undefined;
user: UserType;
whiteRing: boolean | undefined;
+ allowNavigation?: boolean;
}
const Tagg: React.FC<TaggProps> = ({
@@ -45,6 +46,7 @@ const Tagg: React.FC<TaggProps> = ({
userXId,
user,
whiteRing,
+ allowNavigation = true,
}) => {
const navigation = useNavigation();
const [modalVisible, setModalVisible] = useState(false);
@@ -145,7 +147,8 @@ const Tagg: React.FC<TaggProps> = ({
<View style={whiteRing ? styles.spcontainer : styles.container}>
<TouchableOpacity
style={styles.iconTap}
- onPress={modalOrAuthBrowserOrPass}>
+ onPress={modalOrAuthBrowserOrPass}
+ disabled={!allowNavigation}>
<SocialIcon style={styles.icon} social={social} whiteRing />
{pickTheRightRingHere()}
</TouchableOpacity>
diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx
index f952c53f..567b58de 100644
--- a/src/components/taggs/TaggsBar.tsx
+++ b/src/components/taggs/TaggsBar.tsx
@@ -2,7 +2,7 @@ import React, {Fragment, useEffect, useState} from 'react';
import {StyleSheet} from 'react-native';
import Animated from 'react-native-reanimated';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
-import {useDispatch, useSelector} from 'react-redux';
+import {useDispatch, useSelector, useStore} from 'react-redux';
import {
INTEGRATED_SOCIAL_LIST,
PROFILE_CUTOUT_BOTTOM_Y,
@@ -12,6 +12,7 @@ import {getLinkedSocials} from '../../services';
import {loadIndividualSocial, updateSocial} from '../../store/actions';
import {RootState} from '../../store/rootReducer';
import {ScreenType} from '../../types';
+import {canViewProfile} from '../../utils';
import Tagg from './Tagg';
const {View, ScrollView, interpolate, Extrapolate} = Animated;
@@ -33,10 +34,11 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
}) => {
let [taggs, setTaggs] = useState<Object[]>([]);
let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true);
-
- const {user} = userXId
- ? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.user);
+ const {user} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.user,
+ );
+ const state: RootState = useStore().getState();
+ const allowTaggsNavigation = canViewProfile(state, userXId, screenType);
const dispatch = useDispatch();
@@ -79,6 +81,7 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
setTaggsNeedUpdate={setTaggsNeedUpdate}
setSocialDataNeedUpdate={handleSocialUpdate}
whiteRing={whiteRing ? whiteRing : undefined}
+ allowNavigation={allowTaggsNavigation}
/>,
);
i++;
@@ -96,6 +99,7 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
userXId={userXId}
user={user}
whiteRing={whiteRing ? whiteRing : undefined}
+ allowNavigation={allowTaggsNavigation}
/>,
);
i++;
diff --git a/src/constants/strings.ts b/src/constants/strings.ts
index 66d4a6d9..019d0bea 100644
--- a/src/constants/strings.ts
+++ b/src/constants/strings.ts
@@ -55,6 +55,7 @@ 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 NO_NEW_NOTIFICATIONS = 'You have no new notifications';
export const NO_RESULTS_FOUND = 'No Results Found!';
+export const PRIVATE_ACCOUNT = 'This account is private';
export const SUCCESS_BADGES_UPDATE = 'Badges updated successfully!'
export const SUCCESS_CATEGORY_DELETE = 'Category successfully deleted, but its memory will live on';
export const SUCCESS_INVITATION_CODE = 'Welcome to Tagg!';
diff --git a/src/screens/suggestedPeople/SPBody.tsx b/src/screens/suggestedPeople/SPBody.tsx
index c4195fac..824f8b1c 100644
--- a/src/screens/suggestedPeople/SPBody.tsx
+++ b/src/screens/suggestedPeople/SPBody.tsx
@@ -4,17 +4,25 @@ import {StyleSheet, Text, View} from 'react-native';
import {Image} from 'react-native-animatable';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
+import {useStore} from 'react-redux';
import RequestedButton from '../../assets/ionicons/requested-button.svg';
import {TaggsBar} from '../../components';
import {BadgesDropdown, MutualFriends} from '../../components/suggestedPeople';
import {BADGE_DATA} from '../../constants/badges';
+import {RootState} from '../../store/rootReducer';
import {
ProfilePreviewType,
ScreenType,
SuggestedPeopleDataType,
UniversityBadge,
} from '../../types';
-import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {
+ canViewProfile,
+ isIPhoneX,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+} from '../../utils';
interface SPBodyProps {
item: SuggestedPeopleDataType;
@@ -48,6 +56,7 @@ const SPBody: React.FC<SPBodyProps> = ({
}[]
>([]);
const navigation = useNavigation();
+ const state: RootState = useStore().getState();
useEffect(() => {
const newBadges: {badge: UniversityBadge; img: any}[] = [];
const findBadgeIcons = (badge: UniversityBadge) => {
diff --git a/src/services/UserFriendsService.ts b/src/services/UserFriendsService.ts
index da39380f..5c41e988 100644
--- a/src/services/UserFriendsService.ts
+++ b/src/services/UserFriendsService.ts
@@ -1,17 +1,12 @@
-//Abstracted common friends api calls out here
-
import AsyncStorage from '@react-native-community/async-storage';
import {Alert} from 'react-native';
-import {ContactType, FriendshipStatusType} from '../types';
import {
FRIENDS_ENDPOINT,
INVITE_FRIEND_ENDPOINT,
USERS_FROM_CONTACTS_ENDPOINT,
} from '../constants';
-import {
- ERROR_SOMETHING_WENT_WRONG,
- ERROR_SOMETHING_WENT_WRONG_REFRESH,
-} from '../constants/strings';
+import {ERROR_SOMETHING_WENT_WRONG_REFRESH} from '../constants/strings';
+import {ContactType, FriendshipStatusType} from '../types';
export const loadFriends = async (userId: string, token: string) => {
try {
diff --git a/src/store/actions/userFriends.ts b/src/store/actions/userFriends.ts
index 9da3cb4a..4183d0f6 100644
--- a/src/store/actions/userFriends.ts
+++ b/src/store/actions/userFriends.ts
@@ -1,25 +1,24 @@
-import {getTokenOrLogout, userXInStore} from '../../utils';
-import {RootState} from '../rootReducer';
-import {
- FriendshipStatusType,
- ProfilePreviewType,
- ScreenType,
- UserType,
-} from '../../types/types';
+import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
acceptFriendRequestService,
addFriendService,
+ deleteFriendshipService,
friendOrUnfriendUser,
loadFriends,
- deleteFriendshipService,
} from '../../services';
-import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
- userFriendsFetched,
+ FriendshipStatusType,
+ ProfilePreviewType,
+ ScreenType,
+ UserType,
+} from '../../types/types';
+import {getTokenOrLogout, userXInStore} from '../../utils';
+import {
updateFriends,
+ userFriendsFetched,
userXFriendshipEdited,
- userLoggedIn,
} from '../reducers';
+import {RootState} from '../rootReducer';
export const loadFriendsData = (
userId: string,
diff --git a/src/types/types.ts b/src/types/types.ts
index 94fc966e..87d61b2e 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -1,3 +1,5 @@
+import Animated from 'react-native-reanimated';
+
export interface UserType {
userId: string;
username: string;
@@ -206,6 +208,11 @@ export interface CommentNotificationType {
export interface ThreadNotificationType extends CommentNotificationType {
parent_comment: string;
}
+export interface ContentProps {
+ y: Animated.Value<number>;
+ userXId: string | undefined;
+ screenType: ScreenType;
+}
export type NotificationType = {
actor: ProfilePreviewType;
@@ -253,6 +260,7 @@ export type SuggestedPeopleDataType = {
social_links: string[];
suggested_people_url: string;
friendship: FriendshipType;
+ is_private: boolean;
};
export type FriendshipType = {
diff --git a/src/utils/users.ts b/src/utils/users.ts
index d5e44b36..f9d6d6b7 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -174,3 +174,35 @@ export const defaultUserProfile = () => {
const defaultImage = require('../assets/images/avatar-placeholder.png');
return defaultImage;
};
+
+/**
+ * Used to determine whether the logged-in user is able to view userX's private
+ * information or not.
+ *
+ * @param state redux store's root state
+ * @param userXId target userX's id
+ * @param screenType current screen type
+ * @returns true if abel to view private info, false otherwise
+ */
+export const canViewProfile = (
+ state: RootState,
+ userXId: string | undefined,
+ screenType: ScreenType,
+) => {
+ // own profile
+ if (!userXId || state.user.user.userId === userXId) {
+ return true;
+ }
+ // not private
+ if (!(userXId && state.userX[screenType][userXId].profile.is_private)) {
+ return true;
+ }
+ // is friend
+ if (
+ userXId &&
+ state.userX[screenType][userXId].profile.friendship_status === 'friends'
+ ) {
+ return true;
+ }
+ return false;
+};