aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-03-29 18:17:46 -0400
committerIvan Chen <ivan@tagg.id>2021-03-29 18:17:46 -0400
commitd5669f3d08bee68b37f51727e499e84610685422 (patch)
tree8de9664c49e63833504aafd69ca8f965249d412d /src
parentd1e5d18c36af46b450ec7d019550c05b1a78f2db (diff)
parentb0e4fe55be8983079f499b923e953855afeb2c64 (diff)
Merge branch 'master' into tma739-bugfix-profile-onboarding-tutorial
# Conflicts: # src/components/profile/Content.tsx
Diffstat (limited to 'src')
-rw-r--r--src/assets/images/blocked-white@3x.pngbin0 -> 1020 bytes
-rw-r--r--src/assets/images/edit-profile@3x.pngbin0 -> 770 bytes
-rw-r--r--src/assets/images/privacypolicy@3x.pngbin0 -> 519 bytes
-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/assets/images/settings/blocked-white.pngbin0 -> 367 bytes
-rw-r--r--src/assets/images/settings/blocked-white@2x.pngbin0 -> 685 bytes
-rw-r--r--src/assets/images/settings/edit-profile.pngbin0 -> 341 bytes
-rw-r--r--src/assets/images/settings/edit-profile@2x.pngbin0 -> 582 bytes
-rw-r--r--src/assets/images/settings/lock-white.pngbin0 -> 416 bytes
-rw-r--r--src/assets/images/settings/lock-white@2x.pngbin0 -> 723 bytes
-rw-r--r--src/assets/images/settings/lock-white@3x.pngbin0 -> 1000 bytes
-rw-r--r--src/assets/images/settings/privacypolicy.pngbin0 -> 283 bytes
-rw-r--r--src/assets/images/settings/privacypolicy@2x.pngbin0 -> 371 bytes
-rw-r--r--src/assets/images/settings/settings-white.pngbin0 -> 400 bytes
-rw-r--r--src/assets/images/settings/settings-white@2x.pngbin0 -> 871 bytes
-rw-r--r--src/assets/images/settings/settings-white@3x.pngbin0 -> 1393 bytes
-rw-r--r--src/assets/images/settings/settings.pngbin0 -> 508 bytes
-rw-r--r--src/assets/images/settings/settings@2x.pngbin0 -> 967 bytes
-rw-r--r--src/assets/images/settings/termsofuse.pngbin0 -> 373 bytes
-rw-r--r--src/assets/images/settings/termsofuse@2x.pngbin0 -> 600 bytes
-rw-r--r--src/assets/images/settings/white-arrow.pngbin0 -> 240 bytes
-rw-r--r--src/assets/images/settings/white-arrow@2x.pngbin0 -> 355 bytes
-rw-r--r--src/assets/images/settings/white-arrow@3x.pngbin0 -> 415 bytes
-rw-r--r--src/assets/images/settings@3x.pngbin0 -> 1433 bytes
-rw-r--r--src/assets/images/termsofuse@3x.pngbin0 -> 939 bytes
-rw-r--r--src/components/common/TaggPrompt.tsx2
-rw-r--r--src/components/profile/Content.tsx310
-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/ProfileMoreInfoDrawer.tsx24
-rw-r--r--src/components/profile/PublicProfile.tsx297
-rw-r--r--src/components/taggs/Tagg.tsx5
-rw-r--r--src/components/taggs/TaggsBar.tsx14
-rw-r--r--src/constants/api.ts4
-rw-r--r--src/constants/constants.ts52
-rw-r--r--src/constants/strings.ts2
-rw-r--r--src/routes/main/MainStackNavigator.tsx3
-rw-r--r--src/routes/main/MainStackScreen.tsx24
-rw-r--r--src/screens/profile/AccountType.tsx124
-rw-r--r--src/screens/profile/PrivacyScreen.tsx49
-rw-r--r--src/screens/profile/SettingsCell.tsx146
-rw-r--r--src/screens/profile/SettingsScreen.tsx87
-rw-r--r--src/screens/profile/index.ts3
-rw-r--r--src/screens/suggestedPeople/SPBody.tsx11
-rw-r--r--src/screens/suggestedPeople/SuggestedPeopleScreen.tsx8
-rw-r--r--src/services/UserFriendsService.ts9
-rw-r--r--src/services/UserProfileService.ts48
-rw-r--r--src/store/actions/user.ts2
-rw-r--r--src/store/actions/userFriends.ts23
-rw-r--r--src/types/types.ts11
-rw-r--r--src/utils/users.ts34
54 files changed, 1026 insertions, 340 deletions
diff --git a/src/assets/images/blocked-white@3x.png b/src/assets/images/blocked-white@3x.png
new file mode 100644
index 00000000..06eeb651
--- /dev/null
+++ b/src/assets/images/blocked-white@3x.png
Binary files differ
diff --git a/src/assets/images/edit-profile@3x.png b/src/assets/images/edit-profile@3x.png
new file mode 100644
index 00000000..8ba76b18
--- /dev/null
+++ b/src/assets/images/edit-profile@3x.png
Binary files differ
diff --git a/src/assets/images/privacypolicy@3x.png b/src/assets/images/privacypolicy@3x.png
new file mode 100644
index 00000000..aa3402b2
--- /dev/null
+++ b/src/assets/images/privacypolicy@3x.png
Binary files differ
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/assets/images/settings/blocked-white.png b/src/assets/images/settings/blocked-white.png
new file mode 100644
index 00000000..ec3750af
--- /dev/null
+++ b/src/assets/images/settings/blocked-white.png
Binary files differ
diff --git a/src/assets/images/settings/blocked-white@2x.png b/src/assets/images/settings/blocked-white@2x.png
new file mode 100644
index 00000000..1f39ba06
--- /dev/null
+++ b/src/assets/images/settings/blocked-white@2x.png
Binary files differ
diff --git a/src/assets/images/settings/edit-profile.png b/src/assets/images/settings/edit-profile.png
new file mode 100644
index 00000000..c096a32a
--- /dev/null
+++ b/src/assets/images/settings/edit-profile.png
Binary files differ
diff --git a/src/assets/images/settings/edit-profile@2x.png b/src/assets/images/settings/edit-profile@2x.png
new file mode 100644
index 00000000..4753e0a3
--- /dev/null
+++ b/src/assets/images/settings/edit-profile@2x.png
Binary files differ
diff --git a/src/assets/images/settings/lock-white.png b/src/assets/images/settings/lock-white.png
new file mode 100644
index 00000000..04a0a55d
--- /dev/null
+++ b/src/assets/images/settings/lock-white.png
Binary files differ
diff --git a/src/assets/images/settings/lock-white@2x.png b/src/assets/images/settings/lock-white@2x.png
new file mode 100644
index 00000000..b1927922
--- /dev/null
+++ b/src/assets/images/settings/lock-white@2x.png
Binary files differ
diff --git a/src/assets/images/settings/lock-white@3x.png b/src/assets/images/settings/lock-white@3x.png
new file mode 100644
index 00000000..da9ac212
--- /dev/null
+++ b/src/assets/images/settings/lock-white@3x.png
Binary files differ
diff --git a/src/assets/images/settings/privacypolicy.png b/src/assets/images/settings/privacypolicy.png
new file mode 100644
index 00000000..47792a3d
--- /dev/null
+++ b/src/assets/images/settings/privacypolicy.png
Binary files differ
diff --git a/src/assets/images/settings/privacypolicy@2x.png b/src/assets/images/settings/privacypolicy@2x.png
new file mode 100644
index 00000000..31a1aeb2
--- /dev/null
+++ b/src/assets/images/settings/privacypolicy@2x.png
Binary files differ
diff --git a/src/assets/images/settings/settings-white.png b/src/assets/images/settings/settings-white.png
new file mode 100644
index 00000000..f571e4b0
--- /dev/null
+++ b/src/assets/images/settings/settings-white.png
Binary files differ
diff --git a/src/assets/images/settings/settings-white@2x.png b/src/assets/images/settings/settings-white@2x.png
new file mode 100644
index 00000000..6d59c672
--- /dev/null
+++ b/src/assets/images/settings/settings-white@2x.png
Binary files differ
diff --git a/src/assets/images/settings/settings-white@3x.png b/src/assets/images/settings/settings-white@3x.png
new file mode 100644
index 00000000..0019b29c
--- /dev/null
+++ b/src/assets/images/settings/settings-white@3x.png
Binary files differ
diff --git a/src/assets/images/settings/settings.png b/src/assets/images/settings/settings.png
new file mode 100644
index 00000000..7445b8bf
--- /dev/null
+++ b/src/assets/images/settings/settings.png
Binary files differ
diff --git a/src/assets/images/settings/settings@2x.png b/src/assets/images/settings/settings@2x.png
new file mode 100644
index 00000000..8a152c70
--- /dev/null
+++ b/src/assets/images/settings/settings@2x.png
Binary files differ
diff --git a/src/assets/images/settings/termsofuse.png b/src/assets/images/settings/termsofuse.png
new file mode 100644
index 00000000..82e90148
--- /dev/null
+++ b/src/assets/images/settings/termsofuse.png
Binary files differ
diff --git a/src/assets/images/settings/termsofuse@2x.png b/src/assets/images/settings/termsofuse@2x.png
new file mode 100644
index 00000000..5e0fe642
--- /dev/null
+++ b/src/assets/images/settings/termsofuse@2x.png
Binary files differ
diff --git a/src/assets/images/settings/white-arrow.png b/src/assets/images/settings/white-arrow.png
new file mode 100644
index 00000000..7c60d262
--- /dev/null
+++ b/src/assets/images/settings/white-arrow.png
Binary files differ
diff --git a/src/assets/images/settings/white-arrow@2x.png b/src/assets/images/settings/white-arrow@2x.png
new file mode 100644
index 00000000..13cce59a
--- /dev/null
+++ b/src/assets/images/settings/white-arrow@2x.png
Binary files differ
diff --git a/src/assets/images/settings/white-arrow@3x.png b/src/assets/images/settings/white-arrow@3x.png
new file mode 100644
index 00000000..48ed872a
--- /dev/null
+++ b/src/assets/images/settings/white-arrow@3x.png
Binary files differ
diff --git a/src/assets/images/settings@3x.png b/src/assets/images/settings@3x.png
new file mode 100644
index 00000000..4844d30f
--- /dev/null
+++ b/src/assets/images/settings@3x.png
Binary files differ
diff --git a/src/assets/images/termsofuse@3x.png b/src/assets/images/termsofuse@3x.png
new file mode 100644
index 00000000..7d253196
--- /dev/null
+++ b/src/assets/images/termsofuse@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 c0cae4b8..9c33eabc 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,95 +1,56 @@
-import {useFocusEffect, useNavigation} from '@react-navigation/native';
-import React, {useCallback, useEffect, useState, useRef} from 'react';
+import React, {useCallback, useEffect, useRef, 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';
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 {
- 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();
+ const state: RootState = useStore().getState();
/*
* Used to imperatively scroll to the top when presenting the moment tutorial.
@@ -103,35 +64,20 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
/**
* 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);
});
@@ -142,92 +88,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(() => {
- setScrollEnabled(false);
- const navigateToMomentUploadPrompt = () => {
- switch (profile.profile_completion_stage) {
- case 1:
- if (
- momentCategories &&
- momentCategories[0] &&
- !isStageOnePromptClosed
- ) {
- navigation.navigate('MomentUploadPrompt', {
- screenType,
- momentCategory: momentCategories[0],
- profileBodyHeight,
- });
- setIsStageOnePromptClosed(true);
- }
- break;
- case 2:
- setIsStageTwoPromptClosed(false);
- break;
- case 3:
- setIsStageThreePromptClosed(false);
- break;
- default:
- break;
- }
- };
- if (!userXId) {
- setTimeout(() => {
- if (scrollViewRef.current) {
- scrollViewRef.current.getNode().scrollTo({y: 0});
- }
- navigateToMomentUploadPrompt();
- setScrollEnabled(true);
- }, 2000);
- }
- }, [
- profile.profile_completion_stage,
- momentCategories,
- userXId,
- isStageOnePromptClosed,
- profileBodyHeight,
- ]),
- );
-
useEffect(() => {
const isActuallyBlocked = blockedUsers.some(
(cur_user) => user.username === cur_user.username,
);
- if (isBlocked != isActuallyBlocked) {
+ if (isBlocked !== isActuallyBlocked) {
setIsBlocked(isActuallyBlocked);
}
}, [blockedUsers, user]);
@@ -252,37 +117,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
@@ -304,6 +138,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
return (
<Animated.ScrollView
ref={scrollViewRef}
+ contentContainerStyle={styles.contentContainer}
style={styles.container}
onScroll={(e) => handleScroll(e)}
bounces={shouldBounce}
@@ -324,7 +159,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
onLayout,
userXId,
screenType,
- isFriend,
handleBlockUnblock,
isBlocked,
}}
@@ -333,111 +167,31 @@ 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,
+ setScrollEnabled,
+ profileBodyHeight,
+ scrollViewRef,
+ }}
+ />
+ ) : (
+ <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/ProfileMoreInfoDrawer.tsx b/src/components/profile/ProfileMoreInfoDrawer.tsx
index a77a2e84..f70f90d0 100644
--- a/src/components/profile/ProfileMoreInfoDrawer.tsx
+++ b/src/components/profile/ProfileMoreInfoDrawer.tsx
@@ -3,7 +3,6 @@ import React from 'react';
import {Alert, Image, StyleSheet, TouchableOpacity} from 'react-native';
import {useSelector} from 'react-redux';
import MoreIcon from '../../assets/icons/more_horiz-24px.svg';
-import PersonOutline from '../../assets/ionicons/person-outline.svg';
import {TAGG_DARK_BLUE, TAGG_LIGHT_BLUE} from '../../constants';
import {ERROR_ATTEMPT_EDIT_SP} from '../../constants/strings';
import {RootState} from '../../store/rootreducer';
@@ -29,21 +28,19 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
const isOwnProfile = !userXId || userXName === username;
const goToEditProfile = () => {
- navigation.push('EditProfile', {
+ navigation.navigate('EditProfile', {
userId: userId,
username: username,
});
setIsOpen(false);
};
- const goToUpdateSPProfile = () => {
+ const goToSettingsPage = () => {
if (profile.suggested_people_linked === 0) {
Alert.alert(ERROR_ATTEMPT_EDIT_SP);
} else {
// Sending undefined for updatedSelectedBadges to mark that there was no update yet
- navigation.push('UpdateSPPicture', {
- editing: true,
- });
+ navigation.navigate('SettingsScreen');
setIsOpen(false);
}
};
@@ -81,14 +78,21 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
textColor={'black'}
buttons={[
[
- 'Edit Suggested',
- goToUpdateSPProfile,
+ 'Settings',
+ goToSettingsPage,
<Image
- source={require('../../assets/ionicons/suggested-outlined.png')}
+ source={require('../../assets/images/settings/settings.png')}
+ style={styles.image}
+ />,
+ ],
+ [
+ 'Edit Profile',
+ goToEditProfile,
+ <Image
+ source={require('../../assets/images/settings/edit-profile.png')}
style={styles.image}
/>,
],
- ['Edit Profile', goToEditProfile, <PersonOutline color="black" />],
]}
/>
)}
diff --git a/src/components/profile/PublicProfile.tsx b/src/components/profile/PublicProfile.tsx
new file mode 100644
index 00000000..188292f0
--- /dev/null
+++ b/src/components/profile/PublicProfile.tsx
@@ -0,0 +1,297 @@
+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,
+ setScrollEnabled,
+ profileBodyHeight,
+ scrollViewRef,
+}) => {
+ 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(() => {
+ setScrollEnabled(false);
+ const navigateToMomentUploadPrompt = () => {
+ switch (profile.profile_completion_stage) {
+ case 1:
+ if (
+ momentCategories &&
+ momentCategories[0] &&
+ !isStageOnePromptClosed
+ ) {
+ navigation.navigate('MomentUploadPrompt', {
+ screenType,
+ momentCategory: momentCategories[0],
+ profileBodyHeight,
+ });
+ setIsStageOnePromptClosed(true);
+ }
+ break;
+ case 2:
+ setIsStageTwoPromptClosed(false);
+ break;
+ case 3:
+ setIsStageThreePromptClosed(false);
+ break;
+ default:
+ break;
+ }
+ };
+ if (!userXId) {
+ setTimeout(() => {
+ if (scrollViewRef.current) {
+ scrollViewRef.current.getNode().scrollTo({y: 0});
+ }
+ navigateToMomentUploadPrompt();
+ setScrollEnabled(true);
+ }, 2000);
+ }
+ }, [
+ setScrollEnabled,
+ userXId,
+ profile.profile_completion_stage,
+ momentCategories,
+ isStageOnePromptClosed,
+ navigation,
+ screenType,
+ profileBodyHeight,
+ scrollViewRef,
+ ]),
+ );
+
+ /**
+ * 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/api.ts b/src/constants/api.ts
index 6afdf384..22890c33 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -64,3 +64,7 @@ export const DEEPLINK: string = 'https://tinyurl.com/y3o4aec5';
export const LINK_IG_OAUTH: string = `https://www.instagram.com/oauth/authorize/?client_id=205466150510738&redirect_uri=${DEEPLINK}&scope=user_profile,user_media&response_type=code`;
export const LINK_FB_OAUTH: string = `https://www.facebook.com/v8.0/dialog/oauth?client_id=1308555659343609&redirect_uri=${DEEPLINK}&scope=user_posts,public_profile&response_type=code`;
export const LINK_TWITTER_OAUTH: string = API_URL + 'link-twitter-request/';
+
+// Profile Links
+export const COMMUNITY_GUIDELINES: string = 'https://www.tagg.id/community-guidelines';
+export const PRIVACY_POLICY: string = 'https://www.tagg.id/privacy-policy';
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index d24e352e..f533563d 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -186,3 +186,55 @@ export const SP_WIDTH = 375;
export const SP_HEIGHT = 812;
export const SP_PAGE_SIZE = 5;
+
+export const SETTINGS_DATA = {
+ SettingsAndPrivacy: [
+ {
+ title: 'ACCOUNT',
+ data: [
+ {
+ title: 'Suggested People Profile',
+ preimage: require('../assets/images/tagg-logo.png'),
+ postimage: require('../assets/images/settings/white-arrow.png'),
+ },
+ {
+ title: 'Privacy',
+ preimage: require('../assets/images/settings/settings-white.png'),
+ postimage: require('../assets/images/settings/white-arrow.png'),
+ },
+ ],
+ },
+ {
+ title: 'GENERAL',
+ data: [
+ {
+ title: 'Community Guidelines',
+ preimage: require('../assets/images/settings/termsofuse.png'),
+ postimage: require('../assets/images/settings/white-arrow.png'),
+ },
+ {
+ title: 'Privacy Policy',
+ preimage: require('../assets/images/settings/privacypolicy.png'),
+ postimage: require('../assets/images/settings/white-arrow.png'),
+ },
+ ],
+ },
+ ],
+ PrivacyScreen: [
+ {
+ title: '',
+ data: [
+ {
+ title: 'Account Type',
+ preimage: require('../assets/images/settings/lock-white.png'),
+ postimage: require('../assets/images/settings/white-arrow.png'),
+ },
+ // {
+ // title: 'Blocked Accounts',
+ // preimage: require('../assets/images/settings/blocked-white.png'),
+ // postimage: require('../assets/images/settings/white-arrow.png'),
+ // },
+ ],
+ },
+ ],
+};
diff --git a/src/constants/strings.ts b/src/constants/strings.ts
index 66d4a6d9..4f792dcc 100644
--- a/src/constants/strings.ts
+++ b/src/constants/strings.ts
@@ -31,6 +31,7 @@ export const ERROR_NEXT_PAGE = 'There was a problem while loading the next page
export const ERROR_NOT_ONBOARDED = 'You are now on waitlist, please enter your invitation code if you have one';
export const ERROR_PHONE_IN_USE = 'Phone already in use, please try another one';
export const ERROR_PROFILE_CREATION_SHORT = 'Profile creation failed 😓';
+export const ERROR_PROFILE_UPDATE_SHORT = 'Profile update 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_BIRTHDAY = 'Please select your birthday';
@@ -55,6 +56,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/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index bbe9baf0..9b089634 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -28,6 +28,9 @@ export type MainStackParams = {
userXId: string | undefined;
screenType: ScreenType;
};
+ SettingsScreen: {};
+ PrivacyScreen: {};
+ AccountTypeScreen: {};
SocialMediaTaggs: {
socialMediaType: string;
userXId: string | undefined;
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 8cefd3cc..d855f0df 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -6,6 +6,7 @@ import {StyleSheet, Text} from 'react-native';
import {normalize} from 'react-native-elements';
import BackIcon from '../../assets/icons/back-arrow.svg';
import {
+ AccountType,
AnimatedTutorial,
BadgeSelection,
CaptionScreen,
@@ -20,12 +21,14 @@ import {
MomentUploadPromptScreen,
NotificationsScreen,
ProfileScreen,
+ PrivacyScreen,
RequestContactsAccess,
SearchScreen,
SocialMediaTaggs,
SuggestedPeopleScreen,
SuggestedPeopleUploadPictureScreen,
SuggestedPeopleWelcomeScreen,
+ SettingsScreen,
} from '../../screens';
import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import {ScreenType} from '../../types';
@@ -151,6 +154,27 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
}}
/>
<MainStack.Screen
+ name="SettingsScreen"
+ component={SettingsScreen}
+ options={{
+ ...headerBarOptions('white', 'Settings and Privacy'),
+ }}
+ />
+ <MainStack.Screen
+ name="PrivacyScreen"
+ component={PrivacyScreen}
+ options={{
+ ...headerBarOptions('white', 'Privacy'),
+ }}
+ />
+ <MainStack.Screen
+ name="AccountTypeScreen"
+ component={AccountType}
+ options={{
+ ...headerBarOptions('white', 'Account Type'),
+ }}
+ />
+ <MainStack.Screen
name="AnimatedTutorial"
component={AnimatedTutorial}
options={{
diff --git a/src/screens/profile/AccountType.tsx b/src/screens/profile/AccountType.tsx
new file mode 100644
index 00000000..60ed0668
--- /dev/null
+++ b/src/screens/profile/AccountType.tsx
@@ -0,0 +1,124 @@
+import React, {useState} from 'react';
+import {
+ ActivityIndicator,
+ StatusBar,
+ StyleSheet,
+ Switch,
+ Text,
+ View,
+} from 'react-native';
+import {SafeAreaView} from 'react-native-safe-area-context';
+import {useDispatch, useSelector} from 'react-redux';
+import {Background} from '../../components';
+import {updateProfileVisibility} from '../../services';
+import {NO_PROFILE} from '../../store/initialStates';
+import {RootState} from '../../store/rootReducer';
+import {BackgroundGradientType} from '../../types';
+import {getTokenOrLogout} from '../../utils';
+import {normalize} from '../../utils/layouts';
+
+const AccountType: React.FC = () => {
+ const [isPrivateAccount, setIsPrivateAccount] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const {
+ user: {userId, username},
+ profile: {is_private} = NO_PROFILE,
+ } = useSelector((state: RootState) => state.user);
+
+ const dispatch = useDispatch();
+
+ const updateAccountVisibility = async () => {
+ setIsLoading(true);
+ const isPrivate = !isPrivateAccount;
+ setIsPrivateAccount(isPrivate);
+ const token = await getTokenOrLogout(dispatch);
+ await updateProfileVisibility(
+ token,
+ {userId, username},
+ isPrivate,
+ dispatch,
+ );
+ setIsLoading(false);
+ };
+
+ const getAccountText = () => {
+ return is_private ? 'Private Account' : 'Public Account';
+ };
+
+ return (
+ <>
+ <StatusBar barStyle="light-content" />
+ <Background gradientType={BackgroundGradientType.Light}>
+ <SafeAreaView>
+ <View style={styles.container}>
+ <View style={styles.switchContainerStyle}>
+ <Text style={styles.title}>{getAccountText()}</Text>
+ <ActivityIndicator
+ animating={isLoading}
+ size="small"
+ color="white"
+ />
+ {!isLoading && (
+ <Switch
+ trackColor={{false: 'red', true: '#6EE7E7'}}
+ thumbColor={'white'}
+ ios_backgroundColor="transparent"
+ style={styles.switchStyles}
+ value={is_private}
+ onValueChange={updateAccountVisibility}
+ />
+ )}
+ </View>
+
+ <View style={styles.detailContainerStyle}>
+ <Text style={styles.detailTitleStyle}>
+ Enabling a public account will:
+ </Text>
+ <Text style={styles.detailContentStyle}>
+ {'\n'}Everyone can view my posts{'\n'}
+ {'\n'}Everyone can send me friend requests{'\n'}
+ {'\n'}Everyone can tagg me{'\n'}
+ {'\n'}Everyone can send me direct messages
+ </Text>
+ </View>
+ </View>
+ </SafeAreaView>
+ </Background>
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {marginHorizontal: '8%', marginTop: '20%'},
+ title: {
+ alignSelf: 'center',
+ fontSize: normalize(18),
+ fontWeight: '600',
+ lineHeight: normalize(17.9),
+ color: 'white',
+ },
+ switchContainerStyle: {
+ flexDirection: 'row',
+ alignContent: 'center',
+ justifyContent: 'space-between',
+ },
+ detailContainerStyle: {marginTop: '40%'},
+ detailTitleStyle: {
+ fontSize: normalize(19),
+ fontWeight: '700',
+ lineHeight: normalize(22.67),
+ color: 'white',
+ },
+ detailContentStyle: {
+ fontSize: normalize(14),
+ fontWeight: '600',
+ lineHeight: normalize(16.71),
+ color: 'white',
+ },
+ switchStyles: {
+ borderWidth: 2,
+ borderColor: 'white',
+ },
+});
+
+export default AccountType;
diff --git a/src/screens/profile/PrivacyScreen.tsx b/src/screens/profile/PrivacyScreen.tsx
new file mode 100644
index 00000000..17872e24
--- /dev/null
+++ b/src/screens/profile/PrivacyScreen.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import {
+ SectionList,
+ StatusBar,
+ StyleSheet,
+ View,
+ SafeAreaView,
+} from 'react-native';
+import {useSelector} from 'react-redux';
+import {RootState} from 'src/store/rootReducer';
+import {Background} from '../../components';
+import {NO_PROFILE} from '../../store/initialStates';
+import {BackgroundGradientType} from '../../types';
+import {SCREEN_HEIGHT} from '../../utils/layouts';
+import SettingsCell from './SettingsCell';
+import {SETTINGS_DATA} from '../../constants/constants';
+
+const PrivacyScreen: React.FC = () => {
+ const {profile: {is_private} = NO_PROFILE} = useSelector(
+ (state: RootState) => state.user,
+ );
+
+ return (
+ <>
+ <StatusBar barStyle="light-content" />
+ <Background gradientType={BackgroundGradientType.Light}>
+ <SafeAreaView>
+ <View style={styles.container}>
+ <SectionList
+ sections={SETTINGS_DATA.PrivacyScreen}
+ keyExtractor={(item, index) => item.title + index}
+ renderItem={({item: {title, preimage, postimage}}) => (
+ <SettingsCell
+ {...{title, preimage, postimage, isPrivate: is_private}}
+ />
+ )}
+ />
+ </View>
+ </SafeAreaView>
+ </Background>
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {height: SCREEN_HEIGHT, marginHorizontal: '8%', marginTop: '8%'},
+});
+
+export default PrivacyScreen;
diff --git a/src/screens/profile/SettingsCell.tsx b/src/screens/profile/SettingsCell.tsx
new file mode 100644
index 00000000..f5360242
--- /dev/null
+++ b/src/screens/profile/SettingsCell.tsx
@@ -0,0 +1,146 @@
+import {useNavigation} from '@react-navigation/core';
+import React from 'react';
+import {
+ Alert,
+ Image,
+ Linking,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import InAppBrowser from 'react-native-inappbrowser-reborn';
+import {TAGG_PURPLE} from '../../constants';
+import {COMMUNITY_GUIDELINES, PRIVACY_POLICY} from '../../constants/api';
+import {ERROR_ATTEMPT_EDIT_SP} from '../../constants/strings';
+import {normalize, SCREEN_WIDTH} from '../../utils/layouts';
+
+type SettingsCellProps = {
+ title: string;
+ preimage: number;
+ postimage: number;
+ isPrivate?: boolean;
+ suggested_people_linked?: number;
+};
+
+const SettingsCell: React.FC<SettingsCellProps> = ({
+ title,
+ preimage,
+ postimage,
+ isPrivate,
+ suggested_people_linked,
+}) => {
+ const navigation = useNavigation();
+ const goToUpdateSPProfile = () => {
+ if (suggested_people_linked === 0) {
+ Alert.alert(ERROR_ATTEMPT_EDIT_SP);
+ } else {
+ // Sending undefined for updatedSelectedBadges to mark that there was no update yet
+ navigateTo('UpdateSPPicture', {
+ editing: true,
+ });
+ }
+ };
+ const getActions = (type: string) => {
+ switch (type) {
+ case 'Account Type':
+ navigateTo('AccountTypeScreen', {});
+ break;
+ case 'Blocked Accounts':
+ //TODO:
+ break;
+ case 'Suggested People Profile':
+ goToUpdateSPProfile();
+ break;
+ case 'Privacy':
+ navigateTo('PrivacyScreen', {});
+ break;
+ case 'Community Guidelines':
+ openTaggLink(COMMUNITY_GUIDELINES);
+ break;
+ case 'Privacy Policy':
+ openTaggLink(PRIVACY_POLICY);
+ break;
+ default:
+ break;
+ }
+ };
+
+ const openTaggLink = async (url: string) => {
+ try {
+ if (await InAppBrowser.isAvailable()) {
+ await InAppBrowser.open(url, {
+ dismissButtonStyle: 'cancel',
+ preferredBarTintColor: TAGG_PURPLE,
+ preferredControlTintColor: 'white',
+ animated: true,
+ modalPresentationStyle: 'fullScreen',
+ modalTransitionStyle: 'coverVertical',
+ modalEnabled: true,
+ enableBarCollapsing: false,
+ animations: {
+ startEnter: 'slide_in_right',
+ startExit: 'slide_out_left',
+ endEnter: 'slide_in_left',
+ endExit: 'slide_out_right',
+ },
+ });
+ } else Linking.openURL(url);
+ } catch (error) {
+ Alert.alert(error.message);
+ }
+ };
+
+ const navigateTo = (screen: string, options: object) => {
+ navigation.navigate(screen, options);
+ };
+ return (
+ <TouchableOpacity
+ onPress={() => getActions(title)}
+ style={styles.itemStyles}>
+ <Image
+ resizeMode={'cover'}
+ style={styles.preImageStyles}
+ source={preimage}
+ />
+ <View style={styles.titleContainerStyles}>
+ <Text style={styles.titleStyles}>{title}</Text>
+ </View>
+ <View style={[styles.itemStyles, styles.subItemStyles]}>
+ {title === 'Account Type' && (
+ <Text style={[styles.titleStyles, styles.subtitleStyles]}>
+ {isPrivate ? 'Private' : 'Public'}
+ </Text>
+ )}
+ <Image style={styles.postImageStyles} source={postimage} />
+ </View>
+ </TouchableOpacity>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {marginHorizontal: '8%'},
+ itemStyles: {
+ marginTop: 36,
+ flexDirection: 'row',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ },
+ subItemStyles: {position: 'absolute', right: 0},
+ preImageStyles: {width: SCREEN_WIDTH * 0.05, height: SCREEN_WIDTH * 0.05},
+ postImageStyles: {width: 15, height: 15},
+ titleContainerStyles: {marginLeft: '12%'},
+ titleStyles: {
+ fontSize: normalize(15),
+ fontWeight: '600',
+ lineHeight: normalize(17.9),
+ color: 'white',
+ },
+ subtitleStyles: {color: '#C4C4C4', marginRight: 13},
+ tc: {
+ marginVertical: '5%',
+ top: '8%',
+ },
+});
+
+export default SettingsCell;
diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx
new file mode 100644
index 00000000..05e051b5
--- /dev/null
+++ b/src/screens/profile/SettingsScreen.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import {
+ SafeAreaView,
+ SectionList,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {useDispatch, useSelector} from 'react-redux';
+import {logout} from '../../store/actions';
+import {RootState} from 'src/store/rootReducer';
+import {Background} from '../../components';
+import {SETTINGS_DATA} from '../../constants/constants';
+import {BackgroundGradientType} from '../../types';
+import {normalize, SCREEN_HEIGHT} from '../../utils/layouts';
+import SettingsCell from './SettingsCell';
+import {useNavigation} from '@react-navigation/core';
+
+const SettingsScreen: React.FC = () => {
+ const dispatch = useDispatch();
+ const navigation = useNavigation();
+ const {suggested_people_linked} = useSelector(
+ (state: RootState) => state.user.profile,
+ );
+
+ return (
+ <>
+ <StatusBar barStyle="light-content" />
+ <Background gradientType={BackgroundGradientType.Light}>
+ <SafeAreaView>
+ <View style={styles.container}>
+ <SectionList
+ stickySectionHeadersEnabled={false}
+ sections={SETTINGS_DATA.SettingsAndPrivacy}
+ keyExtractor={(item, index) => item.title + index}
+ renderItem={({item: {title, preimage, postimage}}) => (
+ <SettingsCell
+ {...{title, preimage, postimage, suggested_people_linked}}
+ />
+ )}
+ renderSectionHeader={({section: {title}}) => (
+ <View style={styles.headerContainerStyles}>
+ <Text style={styles.headerTextStyles}>{title}</Text>
+ </View>
+ )}
+ ListFooterComponent={() => (
+ <TouchableOpacity
+ style={styles.logoutContainerStyles}
+ onPress={() => {
+ dispatch(logout());
+ navigation.reset({
+ index: 0,
+ routes: [{name: 'SuggestedPeople'}],
+ });
+ }}>
+ <Text style={styles.logoutTextStyles}>Logout</Text>
+ </TouchableOpacity>
+ )}
+ />
+ </View>
+ </SafeAreaView>
+ </Background>
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {height: SCREEN_HEIGHT, marginHorizontal: '8%', marginTop: '8%'},
+ headerContainerStyles: {marginTop: '14%'},
+ headerTextStyles: {
+ fontSize: normalize(18),
+ fontWeight: '600',
+ lineHeight: normalize(21.48),
+ color: '#E9E9E9',
+ },
+ logoutContainerStyles: {marginTop: '20%', marginLeft: '12%'},
+ logoutTextStyles: {
+ fontSize: normalize(20),
+ fontWeight: '600',
+ lineHeight: normalize(23.87),
+ color: 'white',
+ },
+});
+
+export default SettingsScreen;
diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts
index f74946a6..b7efdd3b 100644
--- a/src/screens/profile/index.ts
+++ b/src/screens/profile/index.ts
@@ -7,3 +7,6 @@ export {default as FriendsListScreen} from './FriendsListScreen';
export {default as EditProfile} from './EditProfile';
export {default as MomentUploadPromptScreen} from './MomentUploadPromptScreen';
export {default as InviteFriendsScreen} from './InviteFriendsScreen';
+export {default as SettingsScreen} from './SettingsScreen';
+export {default as PrivacyScreen} from './PrivacyScreen';
+export {default as AccountType} from './AccountType';
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/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
index 336e8b35..a296351f 100644
--- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
+++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
@@ -74,12 +74,18 @@ const SuggestedPeopleScreen: React.FC = () => {
useEffect(() => {
const appendSelf = async () => {
- const self = {
+ const self: SuggestedPeopleDataType = {
user: getUserAsProfilePreviewType(state.user.user, state.user.profile),
mutual_friends: [],
badges: [],
social_links: [],
suggested_people_url: suggestedPeopleImage,
+ university: state.user.profile.university,
+ friendship: {
+ status: 'no_record',
+ requester_id: '',
+ },
+ is_private: false,
};
people.unshift(self);
setPeople(people);
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/services/UserProfileService.ts b/src/services/UserProfileService.ts
index 085787c3..1ce1d0b5 100644
--- a/src/services/UserProfileService.ts
+++ b/src/services/UserProfileService.ts
@@ -1,6 +1,8 @@
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
+import {useEffect} from 'react';
import {Alert} from 'react-native';
+import {loadUserData} from '../store/actions';
import {
EDIT_PROFILE_ENDPOINT,
GET_FB_POSTS_ENDPOINT,
@@ -20,6 +22,7 @@ import {
ERROR_DOUBLE_CHECK_CONNECTION,
ERROR_DUP_OLD_PWD,
ERROR_INVALID_PWD_CODE,
+ ERROR_PROFILE_UPDATE_SHORT,
ERROR_PWD_ACCOUNT,
ERROR_SOMETHING_WENT_WRONG,
ERROR_SOMETHING_WENT_WRONG_REFRESH,
@@ -27,7 +30,12 @@ import {
SUCCESS_PWD_RESET,
SUCCESS_VERIFICATION_CODE_SENT,
} from '../constants/strings';
-import {ProfileInfoType, ProfileType, SocialAccountType} from '../types';
+import {
+ ProfileInfoType,
+ ProfileType,
+ SocialAccountType,
+ UserType,
+} from '../types';
export const loadProfileInfo = async (token: string, userId: string) => {
try {
@@ -75,6 +83,44 @@ export const getProfilePic = async (
}
};
+export const updateProfileVisibility = async (
+ token: string,
+ user: UserType,
+ isPrivateAccount: Boolean,
+ dispatch: Function,
+) => {
+ try {
+ const url = EDIT_PROFILE_ENDPOINT + `${user.userId}/`;
+ const request = new FormData();
+ request.append('is_private', isPrivateAccount ? 'True' : 'False');
+ let response = await fetch(url, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: 'Token ' + token,
+ },
+ body: request,
+ });
+ const {status} = response;
+ let data = await response.json();
+ if (status === 200) {
+ await dispatch(loadUserData(user));
+ } else if (status >= 400) {
+ Alert.alert(
+ ERROR_PROFILE_UPDATE_SHORT,
+ data.error || 'Something went wrong! 😭',
+ );
+ }
+ } catch (error) {
+ debugger;
+ Alert.alert(ERROR_PROFILE_UPDATE_SHORT, ERROR_DOUBLE_CHECK_CONNECTION);
+ return {
+ name: 'Profile update error',
+ description: error,
+ };
+ }
+};
+
const integratedSocialPostsEndpoints: {[social: string]: string} = {
Facebook: GET_FB_POSTS_ENDPOINT,
Instagram: GET_IG_POSTS_ENDPOINT,
diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts
index 46f96d9a..374154da 100644
--- a/src/store/actions/user.ts
+++ b/src/store/actions/user.ts
@@ -1,3 +1,4 @@
+import AsyncStorage from '@react-native-community/async-storage';
import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
getProfilePic,
@@ -157,6 +158,7 @@ export const logout = (): ThunkAction<
Action<string>
> => async (dispatch) => {
try {
+ await AsyncStorage.clear();
dispatch({type: userLoggedIn.type, payload: {userId: '', username: ''}});
} catch (error) {
console.log(error);
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..86f16f05 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,14 @@ export interface CommentNotificationType {
export interface ThreadNotificationType extends CommentNotificationType {
parent_comment: string;
}
+export interface ContentProps {
+ y: Animated.Value<number>;
+ userXId: string | undefined;
+ screenType: ScreenType;
+ setScrollEnabled: (enabled: boolean) => void;
+ profileBodyHeight: number;
+ scrollViewRef: React.MutableRefObject<null>;
+}
export type NotificationType = {
actor: ProfilePreviewType;
@@ -253,6 +263,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..22c1c1f0 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -133,7 +133,7 @@ export const loadAllSocialsForUser = async (userId: string, token?: string) => {
export const getTokenOrLogout = async (dispatch: Function): Promise<string> => {
const token = await AsyncStorage.getItem('token');
if (!token) {
- dispatch({type: userLoggedIn.type, payload: {userId: '', username: ''}});
+ dispatch(logout());
return '';
}
return token;
@@ -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;
+};