diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/comments/CommentsCount.tsx | 2 | ||||
| -rw-r--r-- | src/components/common/TaggPopup.tsx | 19 | ||||
| -rw-r--r-- | src/components/common/TaggPrompt.tsx | 79 | ||||
| -rw-r--r-- | src/components/common/index.ts | 1 | ||||
| -rw-r--r-- | src/components/moments/Moment.tsx | 86 | ||||
| -rw-r--r-- | src/components/notifications/Notification.tsx | 42 | ||||
| -rw-r--r-- | src/components/onboarding/MomentCategory.tsx | 58 | ||||
| -rw-r--r-- | src/components/onboarding/TaggBigInput.tsx | 3 | ||||
| -rw-r--r-- | src/components/onboarding/TaggInput.tsx | 3 | ||||
| -rw-r--r-- | src/components/onboarding/TermsAndConditionsText.tsx | 13 | ||||
| -rw-r--r-- | src/components/profile/Content.tsx | 128 | ||||
| -rw-r--r-- | src/components/profile/ProfilePreview.tsx | 19 | ||||
| -rw-r--r-- | src/components/search/Explore.tsx | 35 | ||||
| -rw-r--r-- | src/components/search/ExploreSection.tsx | 26 | ||||
| -rw-r--r-- | src/components/search/ExploreSectionUser.tsx | 81 |
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', }, |
