aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/comments/CommentsCount.tsx2
-rw-r--r--src/components/common/TaggPopup.tsx19
-rw-r--r--src/components/common/TaggPrompt.tsx79
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/moments/Moment.tsx86
-rw-r--r--src/components/notifications/Notification.tsx42
-rw-r--r--src/components/onboarding/MomentCategory.tsx58
-rw-r--r--src/components/onboarding/TaggBigInput.tsx3
-rw-r--r--src/components/onboarding/TaggInput.tsx3
-rw-r--r--src/components/onboarding/TermsAndConditionsText.tsx13
-rw-r--r--src/components/profile/Content.tsx128
-rw-r--r--src/components/profile/ProfilePreview.tsx19
-rw-r--r--src/components/search/Explore.tsx35
-rw-r--r--src/components/search/ExploreSection.tsx26
-rw-r--r--src/components/search/ExploreSectionUser.tsx81
15 files changed, 469 insertions, 126 deletions
diff --git a/src/components/comments/CommentsCount.tsx b/src/components/comments/CommentsCount.tsx
index 325e2788..f4f8197d 100644
--- a/src/components/comments/CommentsCount.tsx
+++ b/src/components/comments/CommentsCount.tsx
@@ -30,7 +30,7 @@ const CommentsCount: React.FC<CommentsCountProps> = ({
};
return (
<>
- <TouchableOpacity onPress={() => navigateToCommentsScreen()}>
+ <TouchableOpacity onPress={navigateToCommentsScreen}>
<CommentIcon style={styles.image} />
<Text style={styles.count}>
{commentsCount !== '0' ? commentsCount : ''}
diff --git a/src/components/common/TaggPopup.tsx b/src/components/common/TaggPopup.tsx
index db24adb8..86a472b1 100644
--- a/src/components/common/TaggPopup.tsx
+++ b/src/components/common/TaggPopup.tsx
@@ -31,7 +31,11 @@ const TaggPopup: React.FC<TaggPopupProps> = ({route, navigation}) => {
const {messageHeader, messageBody, next} = route.params.popupProps;
return (
- <View style={styles.container}>
+ <TouchableOpacity
+ style={styles.container}
+ onPressOut={() => {
+ navigation.goBack();
+ }}>
<View style={styles.popup}>
<Image
style={styles.icon}
@@ -61,7 +65,7 @@ const TaggPopup: React.FC<TaggPopupProps> = ({route, navigation}) => {
/>
</View>
)}
- </View>
+ </TouchableOpacity>
);
};
@@ -92,23 +96,23 @@ const styles = StyleSheet.create({
},
header: {
color: '#fff',
- fontSize: 16,
+ fontSize: SCREEN_WIDTH / 25,
fontWeight: '600',
textAlign: 'justify',
marginBottom: '2%',
- marginHorizontal: '2%',
+ marginLeft: '4%',
},
subtext: {
color: '#fff',
- fontSize: 12,
+ fontSize: SCREEN_WIDTH / 30,
fontWeight: '600',
textAlign: 'justify',
marginBottom: '15%',
- marginHorizontal: '2%',
+ marginLeft: '3%',
},
popup: {
width: SCREEN_WIDTH * 0.8,
- height: SCREEN_WIDTH * 0.2,
+ height: SCREEN_WIDTH * 0.24,
backgroundColor: 'black',
borderRadius: 8,
flexDirection: 'row',
@@ -116,6 +120,7 @@ const styles = StyleSheet.create({
flexWrap: 'wrap',
position: 'absolute',
bottom: SCREEN_HEIGHT * 0.7,
+ padding: SCREEN_WIDTH / 40,
},
footer: {
marginLeft: '50%',
diff --git a/src/components/common/TaggPrompt.tsx b/src/components/common/TaggPrompt.tsx
new file mode 100644
index 00000000..5cd3ac3f
--- /dev/null
+++ b/src/components/common/TaggPrompt.tsx
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import {Platform, Text, StyleSheet, TouchableOpacity} from 'react-native';
+import {Image, View} from 'react-native-animatable';
+import {SCREEN_HEIGHT} from '../../utils';
+import CloseIcon from '../../assets/ionicons/close-outline.svg';
+
+type TaggPromptProps = {
+ messageHeader: string;
+ messageBody: string;
+ logoType: string;
+ onClose: () => void;
+};
+
+const TaggPrompt: React.FC<TaggPromptProps> = ({
+ messageHeader,
+ messageBody,
+ logoType,
+ onClose,
+}) => {
+ /**
+ * Generic prompt for Tagg
+ */
+
+ return (
+ <View style={styles.container}>
+ <Image
+ style={styles.icon}
+ source={require('../../assets/icons/plus-logo.png')}
+ />
+ <Text style={styles.header}>{messageHeader}</Text>
+ <Text style={styles.subtext}>{messageBody}</Text>
+ <TouchableOpacity
+ style={styles.closeButton}
+ onPress={() => {
+ onClose();
+ }}>
+ <CloseIcon height={'50%'} width={'50%'} color="gray" />
+ </TouchableOpacity>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'white',
+ height: SCREEN_HEIGHT / 4.5,
+ paddingTop: SCREEN_HEIGHT / 10,
+ paddingBottom: SCREEN_HEIGHT / 50,
+ },
+ closeButton: {
+ position: 'relative',
+ height: '40%',
+ bottom: SCREEN_HEIGHT / 6,
+ aspectRatio: 1,
+ alignSelf: 'flex-end',
+ },
+ icon: {
+ width: 40,
+ height: 40,
+ },
+ header: {
+ color: 'black',
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginTop: '2%',
+ },
+ subtext: {
+ color: 'gray',
+ fontSize: 12,
+ fontWeight: '500',
+ textAlign: 'center',
+ marginTop: '2%',
+ },
+});
+export default TaggPrompt;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index d5d36297..9162ec70 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -18,3 +18,4 @@ export {default as BottomDrawer} from './BottomDrawer';
export {default as TaggLoadingTndicator} from './TaggLoadingIndicator';
export {default as GenericMoreInfoDrawer} from './GenericMoreInfoDrawer';
export {default as TaggPopUp} from './TaggPopup';
+export {default as TaggPrompt} from './TaggPrompt';
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
index 0d2c2b62..6dbcd170 100644
--- a/src/components/moments/Moment.tsx
+++ b/src/components/moments/Moment.tsx
@@ -1,25 +1,39 @@
import {useNavigation} from '@react-navigation/native';
-import React from 'react';
-import {Alert, StyleSheet, View} from 'react-native';
+import React, {Fragment} from 'react';
+import {
+ Alert,
+ StyleProp,
+ StyleSheet,
+ View,
+ ViewProps,
+ ViewStyle,
+} from 'react-native';
import {Text} from 'react-native-animatable';
import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler';
import LinearGradient from 'react-native-linear-gradient';
import PlusIcon from '../../assets/icons/plus_icon-01.svg';
+import UpIcon from '../../assets/icons/up_icon.svg';
+import DownIcon from '../../assets/icons/down_icon.svg';
import DeleteIcon from '../../assets/icons/delete-logo.svg';
import BigPlusIcon from '../../assets/icons/plus_icon-02.svg';
import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
import {SCREEN_WIDTH} from '../../utils';
import ImagePicker from 'react-native-image-crop-picker';
import MomentTile from './MomentTile';
-import {MomentCategoryType, MomentType, ScreenType} from 'src/types';
+import {MomentType, ScreenType} from 'src/types';
+import {useDispatch} from 'react-redux';
interface MomentProps {
- title: MomentCategoryType;
+ title: string;
images: MomentType[] | undefined;
userXId: string | undefined;
screenType: ScreenType;
- handleMomentCategoryDelete: (_: MomentCategoryType) => void;
+ handleMomentCategoryDelete: (_: string) => void;
shouldAllowDeletion: boolean;
+ showUpButton: boolean;
+ showDownButton: boolean;
+ move?: (direction: 'up' | 'down', title: string) => void;
+ externalStyles?: Record<string, StyleProp<ViewStyle>>;
}
const Moment: React.FC<MomentProps> = ({
@@ -29,8 +43,13 @@ const Moment: React.FC<MomentProps> = ({
screenType,
handleMomentCategoryDelete,
shouldAllowDeletion,
+ showUpButton,
+ showDownButton,
+ move,
+ externalStyles,
}) => {
const navigation = useNavigation();
+ const dispatch = useDispatch();
const navigateToImagePicker = () => {
ImagePicker.openPicker({
@@ -57,19 +76,50 @@ const Moment: React.FC<MomentProps> = ({
}
})
.catch((err) => {
- Alert.alert('Unable to upload moment!');
+ if (err.code && err.code !== 'E_PICKER_CANCELLED') {
+ Alert.alert('Unable to upload moment!');
+ }
});
};
+
return (
- <View style={styles.container}>
- <View style={styles.header}>
- <Text style={styles.titleText}>{title}</Text>
+ <View style={[styles.container, externalStyles?.container]}>
+ <View style={[styles.header, externalStyles?.header]}>
+ <Text style={[styles.titleText, externalStyles?.titleText]}>
+ {title}
+ </Text>
+ {!userXId ? (
+ <>
+ {showUpButton && move && (
+ <UpIcon
+ width={19}
+ height={19}
+ onPress={() => move('up', title)}
+ color={TAGG_TEXT_LIGHT_BLUE}
+ style={{marginLeft: 5}}
+ />
+ )}
+ {showDownButton && move && (
+ <DownIcon
+ width={19}
+ height={19}
+ onPress={() => move('down', title)}
+ color={TAGG_TEXT_LIGHT_BLUE}
+ style={{marginLeft: 5}}
+ />
+ )}
+ </>
+ ) : (
+ <Fragment />
+ )}
+ <View style={styles.flexer} />
{!userXId ? (
<>
<PlusIcon
width={21}
height={21}
onPress={() => navigateToImagePicker()}
+ color={TAGG_TEXT_LIGHT_BLUE}
style={{marginRight: 10}}
/>
{shouldAllowDeletion && (
@@ -87,7 +137,7 @@ const Moment: React.FC<MomentProps> = ({
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
- style={styles.scrollContainer}>
+ style={[styles.scrollContainer, externalStyles?.scrollContainer]}>
{images &&
images.map((imageObj: MomentType) => (
<MomentTile
@@ -104,7 +154,7 @@ const Moment: React.FC<MomentProps> = ({
<View style={styles.defaultImage}>
<BigPlusIcon width={24} height={24} />
<Text style={styles.defaultImageText}>
- Add a moment of your {title.toLowerCase()}!
+ Add a moment of your {title?.toLowerCase()}!
</Text>
</View>
</LinearGradient>
@@ -123,21 +173,23 @@ const styles = StyleSheet.create({
},
header: {
flex: 1,
- paddingLeft: '3%',
- padding: 5,
- paddingTop: 20,
+ padding: '3%',
backgroundColor: 'white',
flexDirection: 'row',
- justifyContent: 'space-between',
alignItems: 'center',
},
titleText: {
fontSize: 16,
fontWeight: 'bold',
color: TAGG_TEXT_LIGHT_BLUE,
+ },
+ // titleContainer: {
+ // flex: 1,
+ // flexDirection: 'row',
+ // justifyContent: 'flex-end',
+ // },
+ flexer: {
flex: 1,
- flexDirection: 'row',
- justifyContent: 'flex-end',
},
scrollContainer: {
height: SCREEN_WIDTH / 3.25,
diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx
index f6a04526..184e3f27 100644
--- a/src/components/notifications/Notification.tsx
+++ b/src/components/notifications/Notification.tsx
@@ -1,9 +1,22 @@
import {useNavigation} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
-import {Image, StyleSheet, Text, View} from 'react-native';
+import {
+ ActivityIndicatorBase,
+ Alert,
+ Image,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
-import {useDispatch, useStore} from 'react-redux';
+import {useDispatch, useSelector, useStore} from 'react-redux';
+import {MomentCommentsScreen} from '../../screens';
import {loadAvatar} from '../../services';
+import {
+ EMPTY_MOMENTS_LIST,
+ EMPTY_MOMENT_CATEGORIES,
+} from '../../store/initialStates';
+import {userSocialsReducer} from '../../store/reducers';
import {RootState} from '../../store/rootReducer';
import {NotificationType, ScreenType} from '../../types';
import {
@@ -15,6 +28,7 @@ import {
interface NotificationProps {
item: NotificationType;
+ userXId: string | undefined;
screenType: ScreenType;
}
@@ -27,11 +41,16 @@ const Notification: React.FC<NotificationProps> = (props) => {
notification_object,
unread,
},
+ userXId,
screenType,
} = props;
const navigation = useNavigation();
const state: RootState = useStore().getState();
const dispatch = useDispatch();
+ const {moments: loggedInUserMoments} =
+ notification_type === 'CMT'
+ ? useSelector((state: RootState) => state.moments)
+ : {moments: undefined};
const [avatarURI, setAvatarURI] = useState<string | undefined>(undefined);
const [momentURI, setMomentURI] = useState<string | undefined>(undefined);
@@ -81,6 +100,25 @@ const Notification: React.FC<NotificationProps> = (props) => {
screenType,
});
break;
+ case 'CMT':
+ // find the moment we need to display
+ const moment = loggedInUserMoments?.find(
+ (m) => m.moment_id === notification_object?.moment_id,
+ );
+ if (moment) {
+ navigation.push('IndividualMoment', {
+ moment,
+ userXId,
+ screenType,
+ });
+ setTimeout(() => {
+ navigation.push('MomentCommentsScreen', {
+ moment_id: moment.moment_id,
+ screenType,
+ });
+ }, 500);
+ }
+ break;
default:
break;
}
diff --git a/src/components/onboarding/MomentCategory.tsx b/src/components/onboarding/MomentCategory.tsx
index 827ab207..97099b9e 100644
--- a/src/components/onboarding/MomentCategory.tsx
+++ b/src/components/onboarding/MomentCategory.tsx
@@ -1,19 +1,17 @@
-import * as React from 'react';
+import React from 'react';
import {StyleSheet} from 'react-native';
import {Image, Text} from 'react-native-animatable';
import {TouchableOpacity} from 'react-native-gesture-handler';
import LinearGradient from 'react-native-linear-gradient';
-import {BACKGROUND_GRADIENT_MAP} from '../../constants';
-import {MomentCategoryType} from '../../types';
+import {
+ BACKGROUND_GRADIENT_MAP,
+ MOMENT_CATEGORY_BG_COLORS,
+} from '../../constants';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
type MomentCategoryProps = {
- categoryType: MomentCategoryType;
- onSelect: (
- category: MomentCategoryType,
- isSelected: boolean,
- isAdded: boolean,
- ) => void;
+ categoryType: string;
+ onSelect: (category: string, isSelected: boolean, isAdded: boolean) => void;
isSelected: boolean;
isAdded: boolean;
};
@@ -32,63 +30,75 @@ const MomentCategory: React.FC<MomentCategoryProps> = ({
switch (categoryType) {
case 'Friends':
icon = require('../../assets/moment-categories/friends-icon.png');
- bgColor = '#5E4AE4';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[0];
break;
case 'Adventure':
icon = require('../../assets/moment-categories/adventure-icon.png');
- bgColor = '#5044A6';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[1];
break;
case 'Photo Dump':
icon = require('../../assets/moment-categories/photo-dump-icon.png');
- bgColor = '#4755A1';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[2];
break;
case 'Food':
icon = require('../../assets/moment-categories/food-icon.png');
- bgColor = '#444BA8';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[3];
break;
case 'Music':
icon = require('../../assets/moment-categories/music-icon.png');
- bgColor = '#374898';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[4];
break;
case 'Art':
icon = require('../../assets/moment-categories/art-icon.png');
- bgColor = '#3F5C97';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[5];
break;
case 'Sports':
icon = require('../../assets/moment-categories/sports-icon.png');
- bgColor = '#3A649F';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[6];
break;
case 'Fashion':
icon = require('../../assets/moment-categories/fashion-icon.png');
- bgColor = '#386A95';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[7];
break;
case 'Travel':
icon = require('../../assets/moment-categories/travel-icon.png');
- bgColor = '#366D84';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[8];
break;
case 'Pets':
icon = require('../../assets/moment-categories/pets-icon.png');
- bgColor = '#335E76';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[9];
break;
case 'Fitness':
icon = require('../../assets/moment-categories/fitness-icon.png');
- bgColor = '#2E5471';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[10];
break;
case 'DIY':
icon = require('../../assets/moment-categories/diy-icon.png');
- bgColor = '#274765';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[11];
break;
case 'Nature':
icon = require('../../assets/moment-categories/nature-icon.png');
- bgColor = '#225363';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[12];
break;
case 'Early Life':
icon = require('../../assets/moment-categories/early-life-icon.png');
- bgColor = '#365F6A';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[13];
break;
case 'Beauty':
icon = require('../../assets/moment-categories/beauty-icon.png');
- bgColor = '#4E7175';
+ bgColor = MOMENT_CATEGORY_BG_COLORS[14];
+ break;
+ default:
+ // All custom categories
+ icon = require('../../assets/moment-categories/custom-icon.png');
+ // A quick deterministic "random" color picker by summing up ascii char codees
+ const charCodeSum = categoryType
+ .split('')
+ .reduce((acc: number, x: string) => acc + x.charCodeAt(0), 0);
+ bgColor =
+ MOMENT_CATEGORY_BG_COLORS[
+ charCodeSum % MOMENT_CATEGORY_BG_COLORS.length
+ ];
break;
}
diff --git a/src/components/onboarding/TaggBigInput.tsx b/src/components/onboarding/TaggBigInput.tsx
index ba965465..4e8e1ef7 100644
--- a/src/components/onboarding/TaggBigInput.tsx
+++ b/src/components/onboarding/TaggBigInput.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import {View, TextInput, StyleSheet, TextInputProps} from 'react-native';
import * as Animatable from 'react-native-animatable';
+import {TAGG_LIGHT_PURPLE} from '../../constants';
interface TaggBigInputProps extends TextInputProps {
valid?: boolean;
@@ -55,7 +56,7 @@ const styles = StyleSheet.create({
warning: {
fontSize: 14,
marginTop: 5,
- color: '#f4ddff',
+ color: TAGG_LIGHT_PURPLE,
maxWidth: 350,
textAlign: 'center',
},
diff --git a/src/components/onboarding/TaggInput.tsx b/src/components/onboarding/TaggInput.tsx
index 12d99325..405564ab 100644
--- a/src/components/onboarding/TaggInput.tsx
+++ b/src/components/onboarding/TaggInput.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import {View, TextInput, StyleSheet, TextInputProps} from 'react-native';
import * as Animatable from 'react-native-animatable';
+import {TAGG_LIGHT_PURPLE} from '../../constants';
interface TaggInputProps extends TextInputProps {
valid?: boolean;
@@ -52,7 +53,7 @@ const styles = StyleSheet.create({
warning: {
fontSize: 14,
marginTop: 5,
- color: '#f4ddff',
+ color: TAGG_LIGHT_PURPLE,
maxWidth: 350,
textAlign: 'center',
},
diff --git a/src/components/onboarding/TermsAndConditionsText.tsx b/src/components/onboarding/TermsAndConditionsText.tsx
index 39450667..2102d613 100644
--- a/src/components/onboarding/TermsAndConditionsText.tsx
+++ b/src/components/onboarding/TermsAndConditionsText.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
-import {StyleSheet, Text} from 'react-native';
+import {Linking, StyleSheet, Text} from 'react-native';
+import {TAGG_WEBSITE} from '../../constants';
const TermsAndConditionsText: React.FC = () => {
const textWithBulletPoint = (data: string, style: object) => {
@@ -550,8 +551,14 @@ const TermsAndConditionsText: React.FC = () => {
By email: <Text style={styles.link}>support@tagg.id</Text>
</Text>
<Text style={styles.paraLeftAlign}>
- By visiting this page on our website:{' '}
- <Text style={styles.link}>https://www.tagg.id/</Text>
+ By visiting this page on our{' '}
+ <Text
+ style={styles.link}
+ onPress={() => {
+ Linking.openURL(TAGG_WEBSITE + 'terms-and-conditions/');
+ }}>
+ website
+ </Text>
</Text>
</React.Fragment>
);
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 3a304938..1d639a41 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -12,19 +12,14 @@ import {
import Animated from 'react-native-reanimated';
import {
CategorySelectionScreenType,
- MomentCategoryType,
MomentType,
ProfilePreviewType,
ProfileType,
ScreenType,
UserType,
} from '../../types';
-import {
- COVER_HEIGHT,
- MOMENT_CATEGORIES,
- TAGG_TEXT_LIGHT_BLUE,
-} from '../../constants';
-import {fetchUserX, SCREEN_HEIGHT, userLogin} from '../../utils';
+import {COVER_HEIGHT, TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {fetchUserX, moveCategory, SCREEN_HEIGHT, userLogin} from '../../utils';
import TaggsBar from '../taggs/TaggsBar';
import {Moment} from '../moments';
import ProfileBody from './ProfileBody';
@@ -45,12 +40,12 @@ import {
NO_PROFILE,
EMPTY_PROFILE_PREVIEW_LIST,
EMPTY_MOMENTS_LIST,
- MOMENT_CATEGORIES_MAP,
} from '../../store/initialStates';
import {Cover} from '.';
import {TouchableOpacity} from 'react-native-gesture-handler';
-import {useNavigation} from '@react-navigation/native';
+import {useFocusEffect, useNavigation} from '@react-navigation/native';
import GreyPlusLogo from '../../assets/icons/grey-plus-logo.svg';
+import {TaggPrompt} from '../common';
interface ContentProps {
y: Animated.Value<number>;
@@ -77,7 +72,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.moments);
- const {momentCategories = MOMENT_CATEGORIES_MAP} = userXId
+ const {momentCategories = []} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.momentCategories);
@@ -103,12 +98,15 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
const [shouldBounce, setShouldBounce] = useState<boolean>(true);
const [refreshing, setRefreshing] = useState<boolean>(false);
- /**
- * Filter list of categories already selected by user
- */
- const userMomentCategories = MOMENT_CATEGORIES.filter(
- (category) => momentCategories[category] === true,
+ const [isStageTwoPromptClosed, setIsStageTwoPromptClosed] = useState<boolean>(
+ false,
+ );
+ const [isStageOnePromptClosed, setIsStageOnePromptClosed] = useState<boolean>(
+ false,
);
+ const [isStageThreePromptClosed, setIsStageThreePromptClosed] = useState<
+ boolean
+ >(false);
const onRefresh = useCallback(() => {
const refrestState = async () => {
@@ -146,6 +144,61 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
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,
+ ]),
+ );
+
/**
* This hook is called on load of profile and when you update the friends list.
*/
@@ -226,7 +279,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
* Confirm with user before deleting the category
* @param category category to be deleted
*/
- const handleCategoryDeletion = (category: MomentCategoryType) => {
+ const handleCategoryDeletion = (category: string) => {
Alert.alert(
'Category Deletion',
`Are you sure that you want to delete the category ${category} ?`,
@@ -239,7 +292,10 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
text: 'Yes',
onPress: () => {
dispatch(
- updateMomentCategories([category], false, loggedInUser.userId),
+ updateMomentCategories(
+ momentCategories.filter((mc) => mc !== category),
+ false,
+ ),
);
dispatch(deleteUserMomentsForCategory(category));
},
@@ -304,7 +360,35 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
} has not posted any moments yet`}</Text>
</View>
)}
- {userMomentCategories.map(
+ {!userXId &&
+ profile.profile_completion_stage === 2 &&
+ !isStageTwoPromptClosed && (
+ <TaggPrompt
+ messageHeader="Create a new category"
+ messageBody={
+ 'Post your first moment to continue building your digital identity!'
+ }
+ logoType=""
+ onClose={() => {
+ setIsStageTwoPromptClosed(true);
+ }}
+ />
+ )}
+ {!userXId &&
+ profile.profile_completion_stage === 3 &&
+ !isStageThreePromptClosed && (
+ <TaggPrompt
+ messageHeader="Continue to build your profile"
+ messageBody={
+ 'Continue to personalize your own digital space in\nthis community by filling your profile with\ncategories and moments!'
+ }
+ logoType=""
+ onClose={() => {
+ setIsStageThreePromptClosed(true);
+ }}
+ />
+ )}
+ {momentCategories.map(
(title, index) =>
(!userXId || imagesMap.get(title)) && (
<Moment
@@ -314,15 +398,17 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
userXId={userXId}
screenType={screenType}
handleMomentCategoryDelete={handleCategoryDeletion}
- shouldAllowDeletion={userMomentCategories.length > 2}
+ shouldAllowDeletion={momentCategories.length > 1}
+ showUpButton={index !== 0}
+ showDownButton={index !== momentCategories.length - 1}
+ move={move}
/>
),
)}
- {!userXId && userMomentCategories.length < 6 && (
+ {!userXId && (
<TouchableOpacity
onPress={() =>
navigation.push('CategorySelection', {
- categories: momentCategories,
screenType: CategorySelectionScreenType.Profile,
user: loggedInUser,
})
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
index bd015811..134e94cd 100644
--- a/src/components/profile/ProfilePreview.tsx
+++ b/src/components/profile/ProfilePreview.tsx
@@ -134,20 +134,23 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
}
}
+ const userXId =
+ loggedInUser.username === user.username ? undefined : user.id;
+
/**
- * Dispatch an event to Fetch the user details
- * If the user is already present in store, do not fetch again
- * Finally, Navigate to profile of the user selected
+ * Dispatch an event to Fetch the user details only if we're navigating to
+ * a userX's profile.
+ * If the user is already present in store, do not fetch again.
+ * Finally, Navigate to profile of the user selected.
*/
-
- if (!userXInStore(state, screenType, user.id)) {
+ if (userXId && !userXInStore(state, screenType, user.id)) {
await fetchUserX(
dispatch,
{userId: user.id, username: user.username},
screenType,
);
}
- const userXId = loggedInUser.username === user.username ? undefined : user.id;
+
navigation.push('Profile', {
userXId,
screenType,
@@ -205,7 +208,6 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
usernameStyle = styles.searchResultUsername;
nameStyle = styles.searchResultName;
}
-
return (
<TouchableOpacity
onPress={addToRecentlyStoredAndNavigateToProfile}
@@ -257,9 +259,9 @@ const styles = StyleSheet.create({
discoverUsersContainer: {
alignItems: 'center',
textAlign: 'center',
- margin: '0.5%',
width: '32%',
marginVertical: 10,
+ borderWidth: 1,
},
searchResultAvatar: {
height: 60,
@@ -290,6 +292,7 @@ const styles = StyleSheet.create({
discoverUsersNameContainer: {
justifyContent: 'space-evenly',
alignSelf: 'stretch',
+ marginTop: 5,
},
searchResultUsername: {
fontSize: 18,
diff --git a/src/components/search/Explore.tsx b/src/components/search/Explore.tsx
index a02205a4..c07c66b8 100644
--- a/src/components/search/Explore.tsx
+++ b/src/components/search/Explore.tsx
@@ -1,27 +1,18 @@
import React from 'react';
-import {View, StyleSheet} from 'react-native';
+import {StyleSheet, Text, View} from 'react-native';
+import {useSelector} from 'react-redux';
+import {EXPLORE_SECTION_TITLES} from '../../constants';
+import {RootState} from '../../store/rootReducer';
+import {ExploreSectionType} from '../../types';
import ExploreSection from './ExploreSection';
const Explore: React.FC = () => {
- const sections: Array<string> = [
- 'People you follow',
- 'People you may know',
- 'Trending in sports',
- 'Trending on Tagg',
- 'Trending in music',
- ];
- const users: Array<string> = [
- 'Sam Davis',
- 'Becca Smith',
- 'Ann Taylor',
- 'Clara Johnson',
- 'Sarah Jung',
- 'Lila Hernandez',
- ];
+ const {explores} = useSelector((state: RootState) => state.taggUsers);
return (
<View style={styles.container}>
- {sections.map((title) => (
- <ExploreSection key={title} title={title} users={users} />
+ <Text style={styles.header}>Search Profiles</Text>
+ {EXPLORE_SECTION_TITLES.map((title: ExploreSectionType) => (
+ <ExploreSection key={title} title={title} users={explores[title]} />
))}
</View>
);
@@ -30,6 +21,14 @@ const Explore: React.FC = () => {
const styles = StyleSheet.create({
container: {
zIndex: 0,
+ // margin: '5%',
+ },
+ header: {
+ fontWeight: '700',
+ fontSize: 22,
+ color: '#fff',
+ marginBottom: '2%',
+ margin: '5%',
},
});
export default Explore;
diff --git a/src/components/search/ExploreSection.tsx b/src/components/search/ExploreSection.tsx
index 8e826bd9..8e8b4988 100644
--- a/src/components/search/ExploreSection.tsx
+++ b/src/components/search/ExploreSection.tsx
@@ -1,5 +1,6 @@
-import React from 'react';
-import {View, Text, ScrollView, StyleSheet} from 'react-native';
+import React, {Fragment} from 'react';
+import {ScrollView, StyleSheet, Text, View} from 'react-native';
+import {ProfilePreviewType} from '../../types';
import ExploreSectionUser from './ExploreSectionUser';
/**
@@ -9,33 +10,40 @@ import ExploreSectionUser from './ExploreSectionUser';
interface ExploreSectionProps {
title: string;
- users: Array<string>;
+ users: ProfilePreviewType[];
}
const ExploreSection: React.FC<ExploreSectionProps> = ({title, users}) => {
- return (
+ return users.length !== 0 ? (
<View style={styles.container}>
<Text style={styles.header}>{title}</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
- {users.map((name, key) => (
- <ExploreSectionUser {...{name, key}} style={styles.user} />
+ <View style={styles.padding} />
+ {users.map((user) => (
+ <ExploreSectionUser key={user.id} user={user} style={styles.user} />
))}
</ScrollView>
</View>
+ ) : (
+ <Fragment />
);
};
const styles = StyleSheet.create({
container: {
- marginBottom: 30,
+ marginVertical: '5%',
},
header: {
fontWeight: '600',
fontSize: 20,
color: '#fff',
- marginBottom: 20,
+ marginLeft: '5%',
+ marginBottom: '5%',
},
user: {
- marginHorizontal: 15,
+ marginHorizontal: 5,
+ },
+ padding: {
+ width: 10,
},
});
diff --git a/src/components/search/ExploreSectionUser.tsx b/src/components/search/ExploreSectionUser.tsx
index a9fce063..0bf68a20 100644
--- a/src/components/search/ExploreSectionUser.tsx
+++ b/src/components/search/ExploreSectionUser.tsx
@@ -1,12 +1,18 @@
-import React from 'react';
+import {useNavigation} from '@react-navigation/native';
+import React, {useEffect, useState} from 'react';
import {
+ Image,
StyleSheet,
Text,
- ViewProps,
- Image,
TouchableOpacity,
+ ViewProps,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
+import {useDispatch, useSelector, useStore} from 'react-redux';
+import {loadAvatar} from '../../services';
+import {RootState} from '../../store/rootReducer';
+import {ProfilePreviewType, ScreenType} from '../../types';
+import {fetchUserX, userXInStore} from '../../utils';
/**
* Search Screen for user recommendations and a search
@@ -14,14 +20,52 @@ import LinearGradient from 'react-native-linear-gradient';
*/
interface ExploreSectionUserProps extends ViewProps {
- name: string;
+ user: ProfilePreviewType;
}
const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({
- name,
+ user,
style,
}) => {
+ const {id, username, first_name, last_name} = user;
+ const [avatar, setAvatar] = useState<string | null>(null);
+ const navigation = useNavigation();
+ const {user: loggedInUser} = useSelector((state: RootState) => state.user);
+ const state: RootState = useStore().getState();
+ const screenType = ScreenType.Search;
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ let mounted = true;
+ const loadAvatarImage = async () => {
+ const response = await loadAvatar(id, true);
+ if (mounted) {
+ setAvatar(response);
+ }
+ };
+ loadAvatarImage();
+ return () => {
+ mounted = false;
+ };
+ }, [user]);
+
+ const handlePress = async () => {
+ if (!userXInStore(state, screenType, user.id)) {
+ await fetchUserX(
+ dispatch,
+ {userId: user.id, username: user.username},
+ screenType,
+ );
+ }
+ const userXId = loggedInUser.username === user.username ? undefined : id;
+ navigation.push('Profile', {
+ userXId,
+ screenType,
+ });
+ };
+
return (
- <TouchableOpacity style={[styles.container, style]}>
+ <TouchableOpacity style={[styles.container, style]} onPress={handlePress}>
<LinearGradient
colors={['#9F00FF', '#27EAE9']}
useAngle
@@ -29,12 +73,18 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({
angleCenter={{x: 0.5, y: 0.5}}
style={styles.gradient}>
<Image
- source={require('../../assets/images/avatar-placeholder.png')}
+ source={
+ avatar
+ ? {uri: avatar}
+ : require('../../assets/images/avatar-placeholder.png')
+ }
style={styles.profile}
/>
</LinearGradient>
- <Text style={styles.name}>{name}</Text>
- <Text style={styles.username}>{`@${name.split(' ').join('')}`}</Text>
+ <Text style={styles.name} numberOfLines={2}>
+ {first_name} {last_name}
+ </Text>
+ <Text style={styles.username} numberOfLines={1}>{`@${username}`}</Text>
</TouchableOpacity>
);
};
@@ -42,27 +92,30 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({
const styles = StyleSheet.create({
container: {
alignItems: 'center',
+ width: 100,
},
gradient: {
- height: 80,
- width: 80,
+ height: 60,
+ aspectRatio: 1,
borderRadius: 40,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 10,
},
profile: {
- height: 76,
- width: 76,
+ height: 55,
+ aspectRatio: 1,
borderRadius: 38,
},
name: {
fontWeight: '600',
+ flexWrap: 'wrap',
fontSize: 16,
color: '#fff',
+ textAlign: 'center',
},
username: {
- fontWeight: '600',
+ fontWeight: '400',
fontSize: 14,
color: '#fff',
},