From a954d6b6b88485dddc0ccfda634ffd102cb34ccd Mon Sep 17 00:00:00 2001
From: Ashm Walia <40498934+ashmgarv@users.noreply.github.com>
Date: Tue, 22 Dec 2020 08:50:27 -0800
Subject: [TMA 446] Create category (#144)
* Added welcome page
* Working code
* Small fix
* Some more cleanup
* Fixes
* Cleanup
* Fix again
* Use gradient for white bg as well
* Fixed type
---
src/assets/icons/added-border.svg | 1 +
src/assets/icons/delete-logo.png | Bin 0 -> 122662 bytes
src/assets/icons/delete-logo.svg | 1 +
src/assets/icons/plus-logo.png | Bin 0 -> 19640 bytes
src/assets/images/welcome.png | Bin 0 -> 304406 bytes
src/assets/moment-categories/adventure-icon.png | Bin 0 -> 2833 bytes
src/assets/moment-categories/art-icon.png | Bin 0 -> 2929 bytes
src/assets/moment-categories/beauty-icon.png | Bin 0 -> 3906 bytes
src/assets/moment-categories/diy-icon.png | Bin 0 -> 3113 bytes
src/assets/moment-categories/early-life-icon.png | Bin 0 -> 1995 bytes
src/assets/moment-categories/fashion-icon.png | Bin 0 -> 2132 bytes
src/assets/moment-categories/food-icon.png | Bin 0 -> 3465 bytes
src/assets/moment-categories/friends-icon.png | Bin 0 -> 3412 bytes
src/assets/moment-categories/music-icon.png | Bin 0 -> 2363 bytes
src/assets/moment-categories/nature-icon.png | Bin 0 -> 3016 bytes
src/assets/moment-categories/nightlife-icon.png | Bin 0 -> 4288 bytes
src/assets/moment-categories/pets-icon.png | Bin 0 -> 3307 bytes
src/assets/moment-categories/photo-dump-icon.png | Bin 0 -> 3935 bytes
src/assets/moment-categories/sports-icon.png | Bin 0 -> 4040 bytes
src/assets/moment-categories/travel-icon.png | Bin 0 -> 2662 bytes
src/components/common/ComingSoon.tsx | 5 +-
src/components/common/TaggPopup.tsx | 133 ++++++++++++
src/components/common/index.ts | 1 +
src/components/moments/Moment.tsx | 32 ++-
src/components/onboarding/Background.tsx | 12 +-
src/components/onboarding/MomentCategory.tsx | 175 +++++++++++++++
src/components/onboarding/index.ts | 1 +
src/components/profile/Content.tsx | 96 +++++++-
src/constants/api.ts | 1 +
src/constants/constants.ts | 28 +++
src/routes/onboarding/Onboarding.tsx | 44 ++++
src/routes/onboarding/OnboardingStack.tsx | 17 +-
src/routes/profile/Profile.tsx | 12 +
src/routes/profile/ProfileStack.tsx | 6 +-
src/screens/onboarding/CategorySelection.tsx | 241 +++++++++++++++++++++
src/screens/onboarding/Checkpoint.tsx | 5 +-
.../onboarding/InvitationCodeVerification.tsx | 10 +-
src/screens/onboarding/Login.tsx | 18 +-
src/screens/onboarding/PasswordReset.tsx | 5 +-
src/screens/onboarding/PasswordResetRequest.tsx | 6 +-
src/screens/onboarding/ProfileOnboarding.tsx | 3 +-
src/screens/onboarding/RegistrationOne.tsx | 6 +-
src/screens/onboarding/RegistrationThree.tsx | 5 +-
src/screens/onboarding/RegistrationTwo.tsx | 5 +-
src/screens/onboarding/SocialMedia.tsx | 42 ++--
src/screens/onboarding/Verification.tsx | 7 +-
src/screens/onboarding/WelcomeScreen.tsx | 94 ++++++++
src/screens/onboarding/index.ts | 2 +
src/screens/profile/EditProfile.tsx | 4 +-
src/services/MomentCategoryService.ts | 88 ++++++++
src/services/index.ts | 1 +
src/store/actions/index.ts | 1 +
src/store/actions/momentCategories.tsx | 63 ++++++
src/store/actions/userX.ts | 10 +-
src/store/initialStates.ts | 25 ++-
src/store/reducers/index.ts | 1 +
src/store/reducers/momentCategoryReducer.tsx | 22 ++
src/store/reducers/userXReducer.ts | 17 +-
src/store/rootReducer.ts | 2 +
src/types/types.ts | 46 ++++
src/utils/users.ts | 2 +
61 files changed, 1235 insertions(+), 61 deletions(-)
create mode 100644 src/assets/icons/added-border.svg
create mode 100644 src/assets/icons/delete-logo.png
create mode 100644 src/assets/icons/delete-logo.svg
create mode 100644 src/assets/icons/plus-logo.png
create mode 100644 src/assets/images/welcome.png
create mode 100644 src/assets/moment-categories/adventure-icon.png
create mode 100644 src/assets/moment-categories/art-icon.png
create mode 100644 src/assets/moment-categories/beauty-icon.png
create mode 100644 src/assets/moment-categories/diy-icon.png
create mode 100644 src/assets/moment-categories/early-life-icon.png
create mode 100644 src/assets/moment-categories/fashion-icon.png
create mode 100644 src/assets/moment-categories/food-icon.png
create mode 100644 src/assets/moment-categories/friends-icon.png
create mode 100644 src/assets/moment-categories/music-icon.png
create mode 100644 src/assets/moment-categories/nature-icon.png
create mode 100644 src/assets/moment-categories/nightlife-icon.png
create mode 100644 src/assets/moment-categories/pets-icon.png
create mode 100644 src/assets/moment-categories/photo-dump-icon.png
create mode 100644 src/assets/moment-categories/sports-icon.png
create mode 100644 src/assets/moment-categories/travel-icon.png
create mode 100644 src/components/common/TaggPopup.tsx
create mode 100644 src/components/onboarding/MomentCategory.tsx
create mode 100644 src/screens/onboarding/CategorySelection.tsx
create mode 100644 src/screens/onboarding/WelcomeScreen.tsx
create mode 100644 src/services/MomentCategoryService.ts
create mode 100644 src/store/actions/momentCategories.tsx
create mode 100644 src/store/reducers/momentCategoryReducer.tsx
(limited to 'src')
diff --git a/src/assets/icons/added-border.svg b/src/assets/icons/added-border.svg
new file mode 100644
index 00000000..ee6a9da3
--- /dev/null
+++ b/src/assets/icons/added-border.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/delete-logo.png b/src/assets/icons/delete-logo.png
new file mode 100644
index 00000000..54a7228b
Binary files /dev/null and b/src/assets/icons/delete-logo.png differ
diff --git a/src/assets/icons/delete-logo.svg b/src/assets/icons/delete-logo.svg
new file mode 100644
index 00000000..7e8e445e
--- /dev/null
+++ b/src/assets/icons/delete-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/plus-logo.png b/src/assets/icons/plus-logo.png
new file mode 100644
index 00000000..195f28fc
Binary files /dev/null and b/src/assets/icons/plus-logo.png differ
diff --git a/src/assets/images/welcome.png b/src/assets/images/welcome.png
new file mode 100644
index 00000000..46ab4f9f
Binary files /dev/null and b/src/assets/images/welcome.png differ
diff --git a/src/assets/moment-categories/adventure-icon.png b/src/assets/moment-categories/adventure-icon.png
new file mode 100644
index 00000000..33f821ec
Binary files /dev/null and b/src/assets/moment-categories/adventure-icon.png differ
diff --git a/src/assets/moment-categories/art-icon.png b/src/assets/moment-categories/art-icon.png
new file mode 100644
index 00000000..c43d941b
Binary files /dev/null and b/src/assets/moment-categories/art-icon.png differ
diff --git a/src/assets/moment-categories/beauty-icon.png b/src/assets/moment-categories/beauty-icon.png
new file mode 100644
index 00000000..1df48648
Binary files /dev/null and b/src/assets/moment-categories/beauty-icon.png differ
diff --git a/src/assets/moment-categories/diy-icon.png b/src/assets/moment-categories/diy-icon.png
new file mode 100644
index 00000000..2c339489
Binary files /dev/null and b/src/assets/moment-categories/diy-icon.png differ
diff --git a/src/assets/moment-categories/early-life-icon.png b/src/assets/moment-categories/early-life-icon.png
new file mode 100644
index 00000000..91aca375
Binary files /dev/null and b/src/assets/moment-categories/early-life-icon.png differ
diff --git a/src/assets/moment-categories/fashion-icon.png b/src/assets/moment-categories/fashion-icon.png
new file mode 100644
index 00000000..106b81ec
Binary files /dev/null and b/src/assets/moment-categories/fashion-icon.png differ
diff --git a/src/assets/moment-categories/food-icon.png b/src/assets/moment-categories/food-icon.png
new file mode 100644
index 00000000..476521a8
Binary files /dev/null and b/src/assets/moment-categories/food-icon.png differ
diff --git a/src/assets/moment-categories/friends-icon.png b/src/assets/moment-categories/friends-icon.png
new file mode 100644
index 00000000..6b595dd1
Binary files /dev/null and b/src/assets/moment-categories/friends-icon.png differ
diff --git a/src/assets/moment-categories/music-icon.png b/src/assets/moment-categories/music-icon.png
new file mode 100644
index 00000000..8e5d82f9
Binary files /dev/null and b/src/assets/moment-categories/music-icon.png differ
diff --git a/src/assets/moment-categories/nature-icon.png b/src/assets/moment-categories/nature-icon.png
new file mode 100644
index 00000000..2870694c
Binary files /dev/null and b/src/assets/moment-categories/nature-icon.png differ
diff --git a/src/assets/moment-categories/nightlife-icon.png b/src/assets/moment-categories/nightlife-icon.png
new file mode 100644
index 00000000..1e473b6c
Binary files /dev/null and b/src/assets/moment-categories/nightlife-icon.png differ
diff --git a/src/assets/moment-categories/pets-icon.png b/src/assets/moment-categories/pets-icon.png
new file mode 100644
index 00000000..91f65f3c
Binary files /dev/null and b/src/assets/moment-categories/pets-icon.png differ
diff --git a/src/assets/moment-categories/photo-dump-icon.png b/src/assets/moment-categories/photo-dump-icon.png
new file mode 100644
index 00000000..ee5585c3
Binary files /dev/null and b/src/assets/moment-categories/photo-dump-icon.png differ
diff --git a/src/assets/moment-categories/sports-icon.png b/src/assets/moment-categories/sports-icon.png
new file mode 100644
index 00000000..9edd76b8
Binary files /dev/null and b/src/assets/moment-categories/sports-icon.png differ
diff --git a/src/assets/moment-categories/travel-icon.png b/src/assets/moment-categories/travel-icon.png
new file mode 100644
index 00000000..5a913ac7
Binary files /dev/null and b/src/assets/moment-categories/travel-icon.png differ
diff --git a/src/components/common/ComingSoon.tsx b/src/components/common/ComingSoon.tsx
index 16b65b58..d7654a20 100644
--- a/src/components/common/ComingSoon.tsx
+++ b/src/components/common/ComingSoon.tsx
@@ -1,11 +1,14 @@
import * as React from 'react';
import {StyleSheet, View, Text, Image} from 'react-native';
+import {BackgroundGradientType} from '../../types';
import {SCREEN_WIDTH} from '../../utils';
import {Background} from '../onboarding';
const ComingSoon: React.FC = () => {
return (
-
+
Coming Soon
diff --git a/src/components/common/TaggPopup.tsx b/src/components/common/TaggPopup.tsx
new file mode 100644
index 00000000..db24adb8
--- /dev/null
+++ b/src/components/common/TaggPopup.tsx
@@ -0,0 +1,133 @@
+import {RouteProp} from '@react-navigation/native';
+import {StackNavigationProp} from '@react-navigation/stack';
+import * as React from 'react';
+import {Platform, Text, StyleSheet, TouchableOpacity} from 'react-native';
+import {Image, View} from 'react-native-animatable';
+import {ArrowButton} from '..';
+import {OnboardingStackParams} from '../../routes';
+import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import CloseIcon from '../../assets/ionicons/close-outline.svg';
+
+type TaggPopupRouteProps = RouteProp;
+type TaggPopupNavigationProps = StackNavigationProp<
+ OnboardingStackParams,
+ 'TaggPopup'
+>;
+
+interface TaggPopupProps {
+ route: TaggPopupRouteProps;
+ navigation: TaggPopupNavigationProps;
+}
+
+const TaggPopup: React.FC = ({route, navigation}) => {
+ /**
+ * Custom popup / Tutorial screen for Tagg
+ * Just like a Singly Linked List, we have a next node
+ * if (next !== undefined)
+ * Display the next button and navigate to next popup node on click
+ * else
+ * Display close button, navigate back on close
+ */
+ const {messageHeader, messageBody, next} = route.params.popupProps;
+
+ return (
+
+
+
+
+ {messageHeader}
+ {messageBody}
+
+ {!next && (
+ {
+ navigation.goBack();
+ }}>
+
+
+ )}
+
+ {next && (
+
+ {
+ navigation.navigate('TaggPopup', {popupProps: next});
+ }}
+ />
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ whiteColor: {
+ color: 'white',
+ },
+ closeButton: {
+ position: 'relative',
+ height: '50%',
+ aspectRatio: 1,
+ left: '20%',
+ },
+ textContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ },
+ icon: {
+ width: 40,
+ height: 40,
+ marginVertical: '1%',
+ },
+ header: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'justify',
+ marginBottom: '2%',
+ marginHorizontal: '2%',
+ },
+ subtext: {
+ color: '#fff',
+ fontSize: 12,
+ fontWeight: '600',
+ textAlign: 'justify',
+ marginBottom: '15%',
+ marginHorizontal: '2%',
+ },
+ popup: {
+ width: SCREEN_WIDTH * 0.8,
+ height: SCREEN_WIDTH * 0.2,
+ backgroundColor: 'black',
+ borderRadius: 8,
+ flexDirection: 'row',
+ alignSelf: 'auto',
+ flexWrap: 'wrap',
+ position: 'absolute',
+ bottom: SCREEN_HEIGHT * 0.7,
+ },
+ footer: {
+ marginLeft: '50%',
+ flexDirection: 'column-reverse',
+ ...Platform.select({
+ ios: {
+ bottom: '20%',
+ },
+ android: {
+ bottom: '10%',
+ },
+ }),
+ },
+});
+export default TaggPopup;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 661d2f52..d5d36297 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -17,3 +17,4 @@ export {default as TaggDatePicker} from './TaggDatePicker';
export {default as BottomDrawer} from './BottomDrawer';
export {default as TaggLoadingTndicator} from './TaggLoadingIndicator';
export {default as GenericMoreInfoDrawer} from './GenericMoreInfoDrawer';
+export {default as TaggPopUp} from './TaggPopup';
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
index 940b519c..fb6186c8 100644
--- a/src/components/moments/Moment.tsx
+++ b/src/components/moments/Moment.tsx
@@ -5,18 +5,21 @@ 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 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 {MomentType, ScreenType} from 'src/types';
+import {MomentCategoryType, MomentType, ScreenType} from 'src/types';
interface MomentProps {
- title: string;
+ title: MomentCategoryType;
images: MomentType[] | undefined;
userXId: string | undefined;
screenType: ScreenType;
+ handleMomentCategoryDelete: (_: MomentCategoryType) => void;
+ shouldAllowDeletion: boolean;
}
const Moment: React.FC = ({
@@ -24,6 +27,8 @@ const Moment: React.FC = ({
images,
userXId,
screenType,
+ handleMomentCategoryDelete,
+ shouldAllowDeletion,
}) => {
const navigation = useNavigation();
@@ -53,11 +58,21 @@ const Moment: React.FC = ({
{title}
{!userXId ? (
- navigateToImagePicker()}
- />
+ <>
+ navigateToImagePicker()}
+ style={{marginRight: 10}}
+ />
+ {shouldAllowDeletion && (
+ handleMomentCategoryDelete(title)}
+ width={19}
+ height={19}
+ />
+ )}
+ >
) : (
)}
@@ -113,6 +128,9 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: 'bold',
color: TAGG_TEXT_LIGHT_BLUE,
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
},
scrollContainer: {
height: SCREEN_WIDTH / 3.25,
diff --git a/src/components/onboarding/Background.tsx b/src/components/onboarding/Background.tsx
index 054eeff6..fb08e945 100644
--- a/src/components/onboarding/Background.tsx
+++ b/src/components/onboarding/Background.tsx
@@ -8,23 +8,27 @@ import {
SafeAreaView,
} from 'react-native';
import {CenteredView} from '../common';
+import {BackgroundGradientType} from '../../types';
+import {BACKGROUND_GRADIENT_MAP} from '../../constants';
interface BackgroundProps extends ViewProps {
centered?: boolean;
+ gradientType: BackgroundGradientType;
}
const Background: React.FC = (props) => {
+ const {centered, gradientType, children} = props;
return (
- {props.centered ? (
- {props.children}
+ {centered ? (
+ {children}
) : (
- {props.children}
+ {children}
)}
diff --git a/src/components/onboarding/MomentCategory.tsx b/src/components/onboarding/MomentCategory.tsx
new file mode 100644
index 00000000..25e8995a
--- /dev/null
+++ b/src/components/onboarding/MomentCategory.tsx
@@ -0,0 +1,175 @@
+import * as 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 {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+
+type MomentCategoryProps = {
+ categoryType: MomentCategoryType;
+ onSelect: (
+ category: MomentCategoryType,
+ isSelected: boolean,
+ isAdded: boolean,
+ ) => void;
+ isSelected: boolean;
+ isAdded: boolean;
+};
+
+const MomentCategory: React.FC = ({
+ categoryType,
+ isSelected,
+ isAdded,
+ onSelect,
+}) => {
+ var icon, bgColor;
+
+ /**
+ * Choose icon and color based on category type
+ */
+ switch (categoryType) {
+ case 'Friends':
+ icon = require('../../assets/moment-categories/friends-icon.png');
+ bgColor = '#5E4AE4';
+ break;
+ case 'Adventure':
+ icon = require('../../assets/moment-categories/adventure-icon.png');
+ bgColor = '#5044A6';
+ break;
+ case 'Photo Dump':
+ icon = require('../../assets/moment-categories/photo-dump-icon.png');
+ bgColor = '#4755A1';
+ break;
+ case 'Food':
+ icon = require('../../assets/moment-categories/food-icon.png');
+ bgColor = '#444BA8';
+ break;
+ case 'Music':
+ icon = require('../../assets/moment-categories/music-icon.png');
+ bgColor = '#374898';
+ break;
+ case 'Art':
+ icon = require('../../assets/moment-categories/art-icon.png');
+ bgColor = '#3F5C97';
+ break;
+ case 'Sports':
+ icon = require('../../assets/moment-categories/sports-icon.png');
+ bgColor = '#3A649F';
+ break;
+ case 'Fashion':
+ icon = require('../../assets/moment-categories/fashion-icon.png');
+ bgColor = '#386A95';
+ break;
+ case 'Travel':
+ icon = require('../../assets/moment-categories/travel-icon.png');
+ bgColor = '#366D84';
+ break;
+ case 'Pets':
+ icon = require('../../assets/moment-categories/pets-icon.png');
+ bgColor = '#335E76';
+ break;
+ case 'Nightlife':
+ icon = require('../../assets/moment-categories/nightlife-icon.png');
+ bgColor = '#2E5471';
+ break;
+ case 'DIY':
+ icon = require('../../assets/moment-categories/diy-icon.png');
+ bgColor = '#274765';
+ break;
+ case 'Nature':
+ icon = require('../../assets/moment-categories/nature-icon.png');
+ bgColor = '#225363';
+ break;
+ case 'Early Life':
+ icon = require('../../assets/moment-categories/early-life-icon.png');
+ bgColor = '#365F6A';
+ break;
+ case 'Beauty':
+ icon = require('../../assets/moment-categories/beauty-icon.png');
+ bgColor = '#4E7175';
+ break;
+ }
+
+ /**
+ * The Linear Gradient helps us add a gradient border when the category is already added /selected by user
+ * if(isAdded)
+ * gradient background
+ * if(isSelected)
+ * white background
+ * else
+ * transparent background
+ */
+ return (
+
+ onSelect(categoryType, !isSelected, isAdded)}
+ style={[
+ styles.container,
+ styles.touchable,
+ {backgroundColor: bgColor},
+ ]}>
+
+ {categoryType}
+ {isAdded && (
+
+ )}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ gradient: {
+ width: SCREEN_WIDTH / 3.7,
+ height: SCREEN_HEIGHT / 5.8,
+ marginHorizontal: '2%',
+ marginVertical: '2%',
+ },
+ touchable: {
+ width: SCREEN_WIDTH / 4,
+ height: SCREEN_HEIGHT / 6.2,
+ marginHorizontal: '2%',
+ marginVertical: '4%',
+ },
+ container: {
+ borderRadius: 8,
+ shadowRadius: 2,
+ shadowOffset: {width: 0, height: 2},
+ shadowOpacity: 0.4,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ icon: {
+ width: 40,
+ height: 40,
+ marginVertical: '8%',
+ },
+ label: {
+ fontWeight: '500',
+ color: 'white',
+ },
+ tick: {
+ marginTop: '3%',
+ width: 15,
+ height: 15,
+ },
+});
+
+export default MomentCategory;
diff --git a/src/components/onboarding/index.ts b/src/components/onboarding/index.ts
index fde4e0af..b790933f 100644
--- a/src/components/onboarding/index.ts
+++ b/src/components/onboarding/index.ts
@@ -9,3 +9,4 @@ export {default as BirthDatePicker} from './BirthDatePicker';
export {default as TaggDropDown} from './TaggDropDown';
export {default as SocialMediaLinker} from './SocialMediaLinker';
export {default as LinkSocialMedia} from './LinkSocialMedia';
+export {default as MomentCategory} from './MomentCategory';
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index f2e0db0a..7064f775 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,21 +1,29 @@
import React, {useCallback, useEffect, useState} from 'react';
import {
+ Alert,
LayoutChangeEvent,
NativeScrollEvent,
NativeSyntheticEvent,
RefreshControl,
StyleSheet,
+ Text,
View,
} from 'react-native';
import Animated from 'react-native-reanimated';
import {
+ CategorySelectionScreenType,
+ MomentCategoryType,
MomentType,
ProfilePreviewType,
ProfileType,
ScreenType,
UserType,
} from '../../types';
-import {COVER_HEIGHT, defaultMoments} from '../../constants';
+import {
+ COVER_HEIGHT,
+ MOMENT_CATEGORIES,
+ TAGG_TEXT_LIGHT_BLUE,
+} from '../../constants';
import {fetchUserX, SCREEN_HEIGHT, userLogin} from '../../utils';
import TaggsBar from '../taggs/TaggsBar';
import {Moment} from '../moments';
@@ -29,15 +37,19 @@ import {
blockUnblockUser,
loadFollowData,
updateUserXFollowersAndFollowing,
+ updateMomentCategories,
} from '../../store/actions';
import {
NO_USER,
NO_PROFILE,
EMPTY_PROFILE_PREVIEW_LIST,
EMPTY_MOMENTS_LIST,
+ MOMENT_CATEGORIES_MAP,
} from '../../store/initialStates';
import {Cover} from '.';
-import {Background} from '../onboarding';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {useNavigation} from '@react-navigation/native';
+import {deleteMomentCategories} from '../../services';
interface ContentProps {
y: Animated.Value;
@@ -60,6 +72,10 @@ const Content: React.FC = ({y, userXId, screenType}) => {
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.moments);
+ const {momentCategories = MOMENT_CATEGORIES_MAP} = userXId
+ ? useSelector((state: RootState) => state.userX[screenType][userXId])
+ : useSelector((state: RootState) => state.momentCategories);
+
const {blockedUsers = EMPTY_PROFILE_PREVIEW_LIST} = useSelector(
(state: RootState) => state.blocked,
);
@@ -68,6 +84,8 @@ const Content: React.FC = ({y, userXId, screenType}) => {
);
const state = useStore().getState();
+ const navigation = useNavigation();
+
/**
* States
*/
@@ -80,6 +98,13 @@ const Content: React.FC = ({y, userXId, screenType}) => {
const [shouldBounce, setShouldBounce] = useState(true);
const [refreshing, setRefreshing] = useState(false);
+ /**
+ * Filter list of categories already selected by user
+ */
+ const userMomentCategories = MOMENT_CATEGORIES.filter(
+ (category) => momentCategories[category] === true,
+ );
+
const onRefresh = useCallback(() => {
const refrestState = async () => {
if (!userXId) {
@@ -194,6 +219,33 @@ const Content: React.FC = ({y, userXId, screenType}) => {
await dispatch(updateUserXFollowersAndFollowing(user.userId, state));
};
+ /**
+ * Handle deletion of a category
+ * Confirm with user before deleting the category
+ * @param category category to be deleted
+ */
+ const handleCategoryDeletion = (category: MomentCategoryType) => {
+ Alert.alert(
+ 'Category Deletion',
+ `Are you sure that you want to delete the category ${category} ?`,
+ [
+ {
+ text: 'Cancel',
+ style: 'cancel',
+ },
+ {
+ text: 'Yes',
+ onPress: () => {
+ dispatch(
+ updateMomentCategories([category], false, loggedInUser.userId),
+ );
+ },
+ },
+ ],
+ {cancelable: true},
+ );
+ };
+
const handleScroll = (e: NativeSyntheticEvent) => {
/**
* Set the new y position
@@ -239,32 +291,60 @@ const Content: React.FC = ({y, userXId, screenType}) => {
/>
- {defaultMoments.map((title, index) => (
+ {userMomentCategories.map((title, index) => (
2}
/>
))}
+ {!userXId && userMomentCategories.length < 6 && (
+
+ navigation.push('CategorySelection', {
+ categories: momentCategories,
+ screenType: CategorySelectionScreenType.Profile,
+ user: loggedInUser,
+ })
+ }
+ style={styles.createCategoryButton}>
+
+ Create a new category
+
+
+ )}
);
};
const styles = StyleSheet.create({
- refreshControlContainer: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
container: {
flex: 1,
},
momentsContainer: {
backgroundColor: '#f2f2f2',
paddingBottom: SCREEN_HEIGHT / 10,
+ flex: 1,
+ flexDirection: 'column',
+ },
+ createCategoryButton: {
+ backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '70%',
+ height: 30,
+ marginTop: '15%',
+ alignSelf: 'center',
+ },
+ createCategoryButtonLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: 'white',
},
});
diff --git a/src/constants/api.ts b/src/constants/api.ts
index f9ac3d7c..890ef102 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -26,6 +26,7 @@ export const ALL_USERS_ENDPOINT: string = API_URL + 'users/';
export const REPORT_ISSUE_ENDPOINT: string = API_URL + 'report/';
export const BLOCK_USER_ENDPOINT: string = API_URL + 'block/';
export const PASSWORD_RESET_ENDPOINT: string = API_URL + 'password-reset/';
+export const MOMENT_CATEGORY_ENDPOINT: string = API_URL + 'moment-category/';
// Register Social Link (Non-integrated)
export const LINK_SNAPCHAT_ENDPOINT: string = API_URL + 'link-sc/';
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index 3fed8fe6..52a52de6 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -1,3 +1,5 @@
+import {ReactText} from 'react';
+import {BackgroundGradientType, MomentCategoryType} from './../types/';
import {SCREEN_WIDTH, SCREEN_HEIGHT, isIPhoneX} from '../utils';
export const CHIN_HEIGHT = 34;
@@ -102,3 +104,29 @@ export const BROWSABLE_SOCIAL_URLS: Record = {
Instagram: 'https://instagram.com/',
Twitter: 'https://twitter.com/',
};
+
+export const MOMENT_CATEGORIES: Array = [
+ 'Friends',
+ 'Adventure',
+ 'Photo Dump',
+ 'Food',
+ 'Music',
+ 'Art',
+ 'Sports',
+ 'Fashion',
+ 'Travel',
+ 'Pets',
+ 'Nightlife',
+ 'DIY',
+ 'Nature',
+ 'Early Life',
+ 'Beauty',
+];
+
+export const BACKGROUND_GRADIENT_MAP: Record<
+ BackgroundGradientType,
+ Array
+> = {
+ [BackgroundGradientType.Light]: ['#9F00FF', '#27EAE9'],
+ [BackgroundGradientType.Dark]: ['#421566', '#385D5E'],
+};
diff --git a/src/routes/onboarding/Onboarding.tsx b/src/routes/onboarding/Onboarding.tsx
index 63a75934..a3d281f5 100644
--- a/src/routes/onboarding/Onboarding.tsx
+++ b/src/routes/onboarding/Onboarding.tsx
@@ -12,8 +12,11 @@ import {
SocialMedia,
PasswordResetRequest,
PasswordReset,
+ WelcomeScreen,
+ CategorySelection,
} from '../../screens';
import {StackCardInterpolationProps} from '@react-navigation/stack';
+import TaggPopup from '../../components/common/TaggPopup';
const forFade = ({current}: StackCardInterpolationProps) => ({
cardStyle: {
@@ -41,6 +44,47 @@ const Onboarding: React.FC = () => {
gestureEnabled: false,
}}
/>
+
+
+ ({
+ cardStyle: {
+ opacity: progress.interpolate({
+ inputRange: [0, 0.5, 0.9, 1],
+ outputRange: [0, 0.25, 0.7, 1],
+ }),
+ },
+ overlayStyle: {
+ backgroundColor: '#505050',
+ opacity: progress.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, 0.9],
+ extrapolate: 'clamp',
+ }),
+ },
+ }),
+ }}
+ />
;
+ screenType: CategorySelectionScreenType;
+ user: UserType;
+ };
+ TaggPopup: {
+ popupProps: TaggPopupType;
+ };
};
export const OnboardingStack = createStackNavigator();
diff --git a/src/routes/profile/Profile.tsx b/src/routes/profile/Profile.tsx
index 3cb928e5..4c93b1ee 100644
--- a/src/routes/profile/Profile.tsx
+++ b/src/routes/profile/Profile.tsx
@@ -8,6 +8,7 @@ import {
MomentCommentsScreen,
FollowersListScreen,
EditProfile,
+ CategorySelection,
} from '../../screens';
import {ProfileStack, ProfileStackParams} from './ProfileStack';
import {RouteProp} from '@react-navigation/native';
@@ -90,6 +91,17 @@ const Profile: React.FC = ({route}) => {
}}
initialParams={{screenType}}
/>
+
{isProfileStack ? (
) : (
diff --git a/src/routes/profile/ProfileStack.tsx b/src/routes/profile/ProfileStack.tsx
index e7db9f37..bc0a9560 100644
--- a/src/routes/profile/ProfileStack.tsx
+++ b/src/routes/profile/ProfileStack.tsx
@@ -2,7 +2,7 @@
* Note the name userXId here, it refers to the id of the user being visited
*/
import {createStackNavigator} from '@react-navigation/stack';
-import {MomentType, ScreenType} from '../../types';
+import {CategorySelectionScreenType, MomentType, ScreenType} from '../../types';
export type ProfileStackParams = {
Search: {
@@ -41,6 +41,10 @@ export type ProfileStackParams = {
userId: string;
username: string;
};
+ CategorySelection: {
+ categories: Array;
+ screenType: CategorySelectionScreenType;
+ };
};
export const ProfileStack = createStackNavigator();
diff --git a/src/screens/onboarding/CategorySelection.tsx b/src/screens/onboarding/CategorySelection.tsx
new file mode 100644
index 00000000..f92b7e39
--- /dev/null
+++ b/src/screens/onboarding/CategorySelection.tsx
@@ -0,0 +1,241 @@
+import {RouteProp} from '@react-navigation/native';
+import React, {useCallback, useEffect, useState} from 'react';
+import {
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {useDispatch} from 'react-redux';
+import {
+ BackgroundGradientType,
+ CategorySelectionScreenType,
+ MomentCategoryType,
+} from '../../types';
+import {Background, MomentCategory} from '../../components';
+import {MOMENT_CATEGORIES} from '../../constants';
+import {OnboardingStackParams} from '../../routes';
+import {StackNavigationProp} from '@react-navigation/stack';
+import {getTokenOrLogout, userLogin} from '../../utils';
+import {postMomentCategories} from '../../services';
+import {updateMomentCategories} from '../../store/actions/momentCategories';
+import {ScrollView} from 'react-native-gesture-handler';
+
+type CategorySelectionRouteProps = RouteProp<
+ OnboardingStackParams,
+ 'CategorySelection'
+>;
+
+type CategorySelectionNavigationProps = StackNavigationProp<
+ OnboardingStackParams,
+ 'CategorySelection'
+>;
+
+interface CategorySelectionProps {
+ route: CategorySelectionRouteProps;
+ navigation: CategorySelectionNavigationProps;
+}
+
+const CategorySelection: React.FC = ({
+ route,
+ navigation,
+}) => {
+ /**
+ * Same component to be used for category selection while onboarding and while on profile
+ */
+ const {categories, screenType, user} = route.params;
+ const isOnBoarding: boolean =
+ screenType === CategorySelectionScreenType.Onboarding;
+ const {userId, username} = user;
+
+ const [selectedCategories, setSelectedCategories] = useState<
+ Array
+ >([]);
+
+ const dispatch = useDispatch();
+
+ /**
+ * Show the tutorial if a new user is OnBoarding
+ */
+ useEffect(() => {
+ if (isOnBoarding) {
+ navigation.navigate('TaggPopup', {
+ popupProps: {
+ messageHeader: 'Category And Moments',
+ messageBody:
+ 'Use pictures and videos to share different aspects of you',
+ next: {
+ messageHeader: 'Select Categories',
+ messageBody:
+ 'Select between 2 - 6 categories to begin creating moments!',
+ next: undefined,
+ },
+ },
+ });
+ }
+ }, [isOnBoarding]);
+
+ /**
+ * Handle selection of a new category
+ * case isAdded:
+ * Return without doing anything
+ * case isSelected:
+ * Add to the selected categories
+ * case not isSelected:
+ * Remove from the selected categories
+ */
+ const onSelect = (
+ category: MomentCategoryType,
+ isSelected: boolean,
+ isAdded: boolean,
+ ) => {
+ if (isAdded) return;
+ if (isSelected) {
+ setSelectedCategories((prev) => [...prev, category]);
+ } else {
+ setSelectedCategories(
+ selectedCategories.filter((item) => item !== category),
+ );
+ }
+ };
+
+ /**
+ * if onboarding
+ * Count of already added categories will always be 0
+ * else
+ * Calculate number of selected categories by iterating through the user's pre-selected categories
+ */
+ const addedLength = !isOnBoarding
+ ? Object.keys(categories).filter((key) => {
+ return categories[key as MomentCategoryType] === true;
+ }).length
+ : 0;
+
+ const handleButtonPress = async () => {
+ /**
+ * Check for lower and upper bound before creating new categories
+ */
+ const totalCategories = addedLength + selectedCategories.length;
+ if (totalCategories < 2) {
+ Alert.alert('Please select atleast 2 categories');
+ return;
+ } else if (totalCategories > 6) {
+ Alert.alert('You may not add more than 6 categories');
+ return;
+ } else if (selectedCategories.length === 0) {
+ Alert.alert('Please select some categories!');
+ return;
+ }
+ try {
+ if (isOnBoarding) {
+ const token = await getTokenOrLogout(dispatch);
+ await postMomentCategories(selectedCategories, token);
+ userLogin(dispatch, {userId: userId, username: username});
+ } else {
+ dispatch(updateMomentCategories(selectedCategories, true, userId));
+ navigation.goBack();
+ }
+ } catch (error) {
+ console.log(error);
+ Alert.alert('There was a problem');
+ }
+ };
+
+ /**
+ * Using a scroll view to accomodate dynamic category creation later on
+ */
+ return (
+
+
+
+ Create new categories
+
+
+ {MOMENT_CATEGORIES.map((category, index) => (
+
+ ))}
+
+
+
+ {isOnBoarding ? 'Login' : 'Create'}
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ marginBottom: '10%',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ linkerContainer: {
+ flex: 1,
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ alignContent: 'center',
+ marginBottom: '10%',
+ },
+ header: {
+ color: '#fff',
+ fontSize: 22,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '4%',
+ },
+ subtext: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginVertical: '8%',
+ marginHorizontal: '10%',
+ },
+ finalAction: {
+ backgroundColor: 'white',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 150,
+ height: 40,
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#8F01FF',
+ marginBottom: '25%',
+ },
+ finalActionLabel: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: 'black',
+ },
+});
+
+export default CategorySelection;
diff --git a/src/screens/onboarding/Checkpoint.tsx b/src/screens/onboarding/Checkpoint.tsx
index 83a8a2bc..b0b42203 100644
--- a/src/screens/onboarding/Checkpoint.tsx
+++ b/src/screens/onboarding/Checkpoint.tsx
@@ -12,6 +12,7 @@ import {
import {OnboardingStackParams} from '../../routes';
import {RegistrationWizard, Background} from '../../components';
+import {BackgroundGradientType} from '../../types';
type CheckpointRouteProp = RouteProp;
type CheckpointNavigationProp = StackNavigationProp<
@@ -44,7 +45,9 @@ const Checkpoint: React.FC = ({route, navigation}) => {
};
return (
-
+
diff --git a/src/screens/onboarding/InvitationCodeVerification.tsx b/src/screens/onboarding/InvitationCodeVerification.tsx
index 3f0ea124..afdf6d3f 100644
--- a/src/screens/onboarding/InvitationCodeVerification.tsx
+++ b/src/screens/onboarding/InvitationCodeVerification.tsx
@@ -26,7 +26,8 @@ import {
Alert,
Platform,
} from 'react-native';
-import {trackPromise} from 'react-promise-tracker';
+
+import {BackgroundGradientType} from '../../types';
type InvitationCodeVerificationScreenNavigationProp = StackNavigationProp<
OnboardingStackParams,
@@ -86,13 +87,16 @@ const InvitationCodeVerification: React.FC = ({
navigation.navigate('Login')}
+ onPress={() => navigation.navigate('WelcomeScreen')}
/>
);
return (
-
+
Enter the code
diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx
index cb550ef8..1315fdf5 100644
--- a/src/screens/onboarding/Login.tsx
+++ b/src/screens/onboarding/Login.tsx
@@ -17,10 +17,15 @@ import {OnboardingStackParams} from '../../routes/onboarding';
import {Background, TaggInput, SubmitButton} from '../../components';
import {usernameRegex, LOGIN_ENDPOINT} from '../../constants';
import AsyncStorage from '@react-native-community/async-storage';
-import {UserType} from '../../types';
+import {
+ BackgroundGradientType,
+ CategorySelectionScreenType,
+ UserType,
+} from '../../types';
import {useDispatch} from 'react-redux';
import {userLogin} from '../../utils';
import SplashScreen from 'react-native-splash-screen';
+import {MOMENT_CATEGORIES_MAP} from '../../store/initialStates';
type VerificationScreenRouteProp = RouteProp;
type VerificationScreenNavigationProp = StackNavigationProp<
@@ -194,8 +199,8 @@ const Login: React.FC = ({navigation}: LoginProps) => {
/*
* Handles tap on "Get Started" text by resetting fields & navigating to the registration page.
*/
- const goToRegistration = () => {
- navigation.navigate('InvitationCodeVerification');
+ const startRegistrationProcess = () => {
+ navigation.navigate('WelcomeScreen');
setForm({...form, attemptedSubmit: false});
};
@@ -244,7 +249,7 @@ const Login: React.FC = ({navigation}: LoginProps) => {
accessible={true}
accessibilityLabel="Get started"
style={styles.getStarted}
- onPress={goToRegistration}>
+ onPress={startRegistrationProcess}>
Get started!
@@ -252,7 +257,10 @@ const Login: React.FC = ({navigation}: LoginProps) => {
);
return (
-
+
= ({
);
return (
-
+
= ({
);
return (
-
+
= ({
};
return (
-
+
diff --git a/src/screens/onboarding/RegistrationOne.tsx b/src/screens/onboarding/RegistrationOne.tsx
index 3373b903..54c4e210 100644
--- a/src/screens/onboarding/RegistrationOne.tsx
+++ b/src/screens/onboarding/RegistrationOne.tsx
@@ -27,7 +27,7 @@ import {trackPromise} from 'react-promise-tracker';
import {SEND_OTP_ENDPOINT} from '../../constants';
import {phoneRegex} from '../../constants';
-import {VerificationScreenType} from '../../types';
+import {BackgroundGradientType, VerificationScreenType} from '../../types';
type RegistrationScreenOneRouteProp = RouteProp<
OnboardingStackParams,
@@ -138,7 +138,9 @@ const RegistrationOne: React.FC = ({navigation}) => {
);
return (
-
+
= ({
);
return (
-
+
= ({
);
return (
-
+
;
+type SocialMediaNavigationProps = StackNavigationProp<
+ OnboardingStackParams,
+ 'SocialMedia'
+>;
+
interface SocialMediaProps {
route: SocialMediaRouteProps;
+ navigation: SocialMediaNavigationProps;
}
-const SocialMedia: React.FC = ({route}) => {
+const SocialMedia: React.FC = ({route, navigation}) => {
const {userId, username} = route.params;
const linkers: Array = [];
@@ -56,17 +67,18 @@ const SocialMedia: React.FC = ({route}) => {
// });
// };
- const handleLogin = () => {
- try {
- userLogin(dispatch, {userId: userId, username: username});
- } catch (error) {
- console.log(error);
- Alert.alert('There was a problem logging you in');
- }
+ const handleNext = () => {
+ navigation.navigate('CategorySelection', {
+ categories: MOMENT_CATEGORIES_MAP,
+ screenType: CategorySelectionScreenType.Onboarding,
+ user: {userId: userId, username: username},
+ });
};
return (
-
+
= ({route}) => {
))}
-
- Login
+
+ Next
);
@@ -133,7 +145,7 @@ const styles = StyleSheet.create({
marginBottom: '35%',
marginHorizontal: '10%',
},
- loginButton: {
+ nextButton: {
backgroundColor: '#8F01FF',
justifyContent: 'center',
alignItems: 'center',
@@ -144,7 +156,7 @@ const styles = StyleSheet.create({
borderColor: '#8F01FF',
marginBottom: '15%',
},
- loginButtonLabel: {
+ nextButtonLabel: {
fontSize: 16,
fontWeight: '500',
color: '#ddd',
diff --git a/src/screens/onboarding/Verification.tsx b/src/screens/onboarding/Verification.tsx
index 9fa1c12f..c808f30b 100644
--- a/src/screens/onboarding/Verification.tsx
+++ b/src/screens/onboarding/Verification.tsx
@@ -27,7 +27,7 @@ import {
Platform,
} from 'react-native';
import {trackPromise} from 'react-promise-tracker';
-import {VerificationScreenType} from '../../types';
+import {BackgroundGradientType, VerificationScreenType} from '../../types';
import {
handlePasswordCodeVerification,
sendOtp,
@@ -137,7 +137,10 @@ const Verification: React.FC = ({route, navigation}) => {
);
return (
-
+
{isPhoneVerification ? (
) : (
diff --git a/src/screens/onboarding/WelcomeScreen.tsx b/src/screens/onboarding/WelcomeScreen.tsx
new file mode 100644
index 00000000..fcdd9bc5
--- /dev/null
+++ b/src/screens/onboarding/WelcomeScreen.tsx
@@ -0,0 +1,94 @@
+import * as React from 'react';
+import {StyleSheet, View, Text, Image, TouchableOpacity} from 'react-native';
+import {SCREEN_WIDTH} from '../../utils';
+import {Background} from '../../components';
+import {OnboardingStackParams} from '../../routes';
+import {StackNavigationProp} from '@react-navigation/stack';
+import {BackgroundGradientType} from '../../types';
+
+type WelcomeScreenNavigationProps = StackNavigationProp<
+ OnboardingStackParams,
+ 'WelcomeScreen'
+>;
+
+interface WelcomeScreenProps {
+ navigation: WelcomeScreenNavigationProps;
+}
+
+const WelcomeScreen: React.FC = ({navigation}) => {
+ const handleNext = () => {
+ navigation.navigate('InvitationCodeVerification');
+ };
+ return (
+
+
+
+
+ Welcome to TAGG!
+
+ This is the new social networking platform for you! It will help you
+ create your own personalized digital space where you can express who
+ you are, along with all the moments that comprehensively define you!
+
+
+
+ Next
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ /**
+ * Set primary axis to column
+ * Align items to centre along that primary axis and the secondary axis as well
+ */
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ image: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_WIDTH,
+ },
+ header: {
+ color: '#fff',
+ fontSize: 32,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '4%',
+ marginHorizontal: '10%',
+ },
+ subtext: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '15%',
+ marginHorizontal: '10%',
+ },
+ nextButton: {
+ backgroundColor: '#8F01FF',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '70%',
+ height: '10%',
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#8F01FF',
+ marginBottom: '15%',
+ },
+ nextButtonLabel: {
+ fontSize: 30,
+ fontWeight: '500',
+ color: '#ddd',
+ },
+});
+export default WelcomeScreen;
diff --git a/src/screens/onboarding/index.ts b/src/screens/onboarding/index.ts
index 2411a7e7..ec833929 100644
--- a/src/screens/onboarding/index.ts
+++ b/src/screens/onboarding/index.ts
@@ -9,3 +9,5 @@ export {default as InvitationCodeVerification} from './InvitationCodeVerificatio
export {default as SocialMedia} from './SocialMedia';
export {default as PasswordResetRequest} from './PasswordResetRequest';
export {default as PasswordReset} from './PasswordReset';
+export {default as WelcomeScreen} from './WelcomeScreen';
+export {default as CategorySelection} from './CategorySelection';
diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx
index 50e1c006..316ad5d4 100644
--- a/src/screens/profile/EditProfile.tsx
+++ b/src/screens/profile/EditProfile.tsx
@@ -39,6 +39,7 @@ import {HeaderHeight, SCREEN_HEIGHT} from '../../utils';
import {RootState} from '../../store/rootReducer';
import {useDispatch, useSelector} from 'react-redux';
import {loadUserData} from '../../store/actions';
+import {BackgroundGradientType} from '../../types';
type EditProfileNavigationProp = StackNavigationProp<
ProfileStackParams,
@@ -219,7 +220,6 @@ const EditProfile: React.FC = ({route, navigation}) => {
});
};
-
const handleSnapchatUpdate = (newUsername: string) => {
// Allow any username, empty means to "un-link" it
// TODO: refresh taggs bar after
@@ -373,7 +373,7 @@ const EditProfile: React.FC = ({route, navigation}) => {
}, [navigation, handleSubmit]);
return (
-
+
diff --git a/src/services/MomentCategoryService.ts b/src/services/MomentCategoryService.ts
new file mode 100644
index 00000000..8bdb70d2
--- /dev/null
+++ b/src/services/MomentCategoryService.ts
@@ -0,0 +1,88 @@
+import {Alert} from 'react-native';
+import {MomentCategoryType} from './../types/types';
+import {MOMENT_CATEGORY_ENDPOINT} from '../constants';
+
+export const loadMomentCategories: (
+ userId: string,
+ token: string,
+) => Promise = async (userId, token) => {
+ let categories: MomentCategoryType[] = [];
+ try {
+ const response = await fetch(MOMENT_CATEGORY_ENDPOINT + `${userId}/`, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ const status = response.status;
+ if (status === 200) {
+ const data = await response.json();
+ categories = data['categories'];
+ } else {
+ console.log('Could not load categories!');
+ return [];
+ }
+ } catch (err) {
+ console.log(err);
+ return [];
+ }
+ return categories;
+};
+
+export const postMomentCategories: (
+ categories: Array,
+ token: string,
+) => Promise = async (categories, token) => {
+ let success = false;
+ try {
+ const response = await fetch(MOMENT_CATEGORY_ENDPOINT, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Token ' + token,
+ },
+ body: JSON.stringify({categories}),
+ });
+ const status = response.status;
+ if (status === 200) {
+ success = true;
+ } else {
+ Alert.alert('There was a problem creating categories!');
+ console.log('Could not post categories!');
+ }
+ } catch (err) {
+ console.log(err);
+ return success;
+ }
+ return success;
+};
+
+export const deleteMomentCategories: (
+ categories: Array,
+ userId: string,
+ token: string,
+) => Promise = async (categories, userId, token) => {
+ let success = false;
+ try {
+ const response = await fetch(MOMENT_CATEGORY_ENDPOINT + `${userId}/`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Token ' + token,
+ },
+ body: JSON.stringify({categories}),
+ });
+ const status = response.status;
+ if (status === 200) {
+ Alert.alert(`The category was successfully deleted!`);
+ success = true;
+ } else {
+ Alert.alert('There was a problem while deleteing category!');
+ console.log('Could not delete category!');
+ }
+ } catch (err) {
+ console.log(err);
+ return success;
+ }
+ return success;
+};
diff --git a/src/services/index.ts b/src/services/index.ts
index bce3a75a..d98996ba 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -5,3 +5,4 @@ export * from './ExploreServices';
export * from './UserFollowServices';
export * from './ReportingService';
export * from './BlockUserService';
+export * from './MomentCategoryService';
diff --git a/src/store/actions/index.ts b/src/store/actions/index.ts
index 04fa9767..f9fd5e9c 100644
--- a/src/store/actions/index.ts
+++ b/src/store/actions/index.ts
@@ -1,6 +1,7 @@
export * from './user';
export * from './userFollow';
export * from './userMoments';
+export * from './momentCategories';
export * from './socials';
export * from './taggUsers';
export * from './userBlock';
diff --git a/src/store/actions/momentCategories.tsx b/src/store/actions/momentCategories.tsx
new file mode 100644
index 00000000..a522c3e0
--- /dev/null
+++ b/src/store/actions/momentCategories.tsx
@@ -0,0 +1,63 @@
+import {RootState} from '../rootReducer';
+import {
+ deleteMomentCategories,
+ loadMomentCategories,
+ postMomentCategories,
+} from '../../services';
+import {Action, ThunkAction} from '@reduxjs/toolkit';
+import {momentCategoriesFetched} from '../reducers';
+import {getTokenOrLogout} from '../../utils';
+import {MomentCategoryType} from '../../types';
+
+/**
+ * Load all categories for user
+ * @param userId id of the user for whom categories should be loaded
+ */
+export const loadUserMomentCategories = (
+ userId: string,
+): ThunkAction, RootState, unknown, Action> => async (
+ dispatch,
+) => {
+ try {
+ const token = await getTokenOrLogout(dispatch);
+ const categories = await loadMomentCategories(userId, token);
+ dispatch({
+ type: momentCategoriesFetched.type,
+ payload: {categories, add: true},
+ });
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+/**
+ * Handle addition / deletion of categories for a user
+ * @param categories List of categories
+ * @param add boolean, if true, we add new categories, else we delete
+ * @param userId id of the user for whom categories should be updated
+ */
+export const updateMomentCategories = (
+ categories: Array,
+ add: boolean,
+ userId: string,
+): ThunkAction, RootState, unknown, Action> => async (
+ dispatch,
+) => {
+ try {
+ const token = await getTokenOrLogout(dispatch);
+ let success = false;
+ if (add) {
+ success = await postMomentCategories(categories, token);
+ } else {
+ success = await deleteMomentCategories(categories, userId, token);
+ }
+ if (success) {
+ dispatch({
+ type: momentCategoriesFetched.type,
+ payload: {categories, add},
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
diff --git a/src/store/actions/userX.ts b/src/store/actions/userX.ts
index 5468f762..87162eb1 100644
--- a/src/store/actions/userX.ts
+++ b/src/store/actions/userX.ts
@@ -1,6 +1,7 @@
+import {loadMomentCategories} from './../../services/MomentCategoryService';
import {userXInStore} from './../../utils/';
import {getTokenOrLogout, loadAllSocialsForUser} from './../../utils';
-import {UserType, ScreenType, ProfilePreviewType} from '../../types/types';
+import {UserType, ScreenType} from '../../types/types';
import {RootState} from '../rootReducer';
import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
@@ -13,6 +14,7 @@ import {
userXProfileFetched,
userXSocialsFetched,
userXUserFetched,
+ userXMomentCategoriesFetched,
resetScreen,
} from '../reducers';
import {
@@ -80,6 +82,12 @@ export const loadUserX = (
payload: {screenType, userId, data},
}),
);
+ loadMomentCategories(userId, token).then((data) => {
+ dispatch({
+ type: userXMomentCategoriesFetched.type,
+ payload: {screenType, userId, data},
+ });
+ });
} catch (error) {
console.log(error);
}
diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts
index 817af86b..8f4a2e84 100644
--- a/src/store/initialStates.ts
+++ b/src/store/initialStates.ts
@@ -1,4 +1,4 @@
-import {MomentType} from 'src/types';
+import {MomentCategoryType, MomentType} from '../types';
import {
ProfileType,
SocialAccountType,
@@ -62,6 +62,24 @@ export const NO_BLOCKED_USERS = {
blockedUsers: EMPTY_PROFILE_PREVIEW_LIST,
};
+export const MOMENT_CATEGORIES_MAP: Record = {
+ Friends: false,
+ Adventure: false,
+ 'Photo Dump': false,
+ Food: false,
+ Music: false,
+ Art: false,
+ Sports: false,
+ Fashion: false,
+ Travel: false,
+ Pets: false,
+ Nightlife: false,
+ DIY: false,
+ Nature: false,
+ 'Early Life': false,
+ Beauty: false,
+};
+
/**
* The dummy userId and username serve the purpose of preventing app crash
* For instance, if it may happen that data in our store is not loaded yet for the userXId being visited.
@@ -74,6 +92,7 @@ export const EMPTY_USER_X = {
followers: EMPTY_PROFILE_PREVIEW_LIST,
following: EMPTY_PROFILE_PREVIEW_LIST,
moments: EMPTY_MOMENTS_LIST,
+ momentCategories: MOMENT_CATEGORIES_MAP,
socialAccounts: NO_SOCIAL_ACCOUNTS,
user: NO_USER,
profile: NO_PROFILE,
@@ -95,3 +114,7 @@ export const EMPTY_SCREEN_TO_USERS_LIST: Record<
[ScreenType.Profile]: EMPTY_USERX_LIST,
[ScreenType.Search]: EMPTY_USERX_LIST,
};
+
+export const INITIAL_CATEGORIES_STATE = {
+ momentCategories: MOMENT_CATEGORIES_MAP,
+};
diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts
index 0e378bc5..e09b41ee 100644
--- a/src/store/reducers/index.ts
+++ b/src/store/reducers/index.ts
@@ -5,3 +5,4 @@ export * from './userSocialsReducer';
export * from './taggUsersReducer';
export * from './userBlockReducer';
export * from './userXReducer';
+export * from './momentCategoryReducer';
diff --git a/src/store/reducers/momentCategoryReducer.tsx b/src/store/reducers/momentCategoryReducer.tsx
new file mode 100644
index 00000000..d1f448f9
--- /dev/null
+++ b/src/store/reducers/momentCategoryReducer.tsx
@@ -0,0 +1,22 @@
+import {createSlice} from '@reduxjs/toolkit';
+import {INITIAL_CATEGORIES_STATE} from '../initialStates';
+import {MomentCategoryType} from '../../types';
+
+const momentCategoriesSlice = createSlice({
+ name: 'momentCategories',
+ initialState: INITIAL_CATEGORIES_STATE,
+ reducers: {
+ /**
+ * One stop to add / delete / update categories for a user
+ */
+ momentCategoriesFetched: (state, action) => {
+ const categories: Array = action.payload.categories;
+ for (let category of categories) {
+ state.momentCategories[category] = action.payload.add;
+ }
+ },
+ },
+});
+
+export const {momentCategoriesFetched} = momentCategoriesSlice.actions;
+export const momentCategoriesReducer = momentCategoriesSlice.reducer;
diff --git a/src/store/reducers/userXReducer.ts b/src/store/reducers/userXReducer.ts
index 154dd7dc..bb142864 100644
--- a/src/store/reducers/userXReducer.ts
+++ b/src/store/reducers/userXReducer.ts
@@ -1,4 +1,4 @@
-import {ScreenType} from '../../types/types';
+import {MomentCategoryType, ScreenType} from '../../types/types';
import {EMPTY_SCREEN_TO_USERS_LIST, EMPTY_USER_X} from '../initialStates';
import {createSlice} from '@reduxjs/toolkit';
@@ -23,31 +23,45 @@ const userXSlice = createSlice({
action.payload.user;
},
+ userXMomentCategoriesFetched: (state, action) => {
+ const categories: Array = action.payload.data;
+ for (let category of categories) {
+ state[action.payload.screenType][
+ action.payload.userId
+ ].momentCategories[category] = true;
+ }
+ },
+
userXMomentsFetched: (state, action) => {
state[action.payload.screenType][
action.payload.userId
].moments = action.payload.data;
},
+
userXFollowersFetched: (state, action) => {
state[action.payload.screenType][
action.payload.userId
].followers = action.payload.data;
},
+
userXFollowingFetched: (state, action) => {
state[action.payload.screenType][
action.payload.userId
].following = action.payload.data;
},
+
userXAvatarFetched: (state, action) => {
state[action.payload.screenType][
action.payload.userId
].avatar = action.payload.data;
},
+
userXCoverFetched: (state, action) => {
state[action.payload.screenType][
action.payload.userId
].cover = action.payload.data;
},
+
userXSocialsFetched: (state, action) => {
state[action.payload.screenType][
action.payload.userId
@@ -72,6 +86,7 @@ export const {
userXMomentsFetched,
userXProfileFetched,
userXSocialsFetched,
+ userXMomentCategoriesFetched,
resetScreen,
} = userXSlice.actions;
export const userXReducer = userXSlice.reducer;
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 695ed8c7..8f002de0 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -7,6 +7,7 @@ import {
taggUsersReducer,
userBlockReducer,
userXReducer,
+ momentCategoriesReducer,
} from './reducers';
/**
@@ -20,6 +21,7 @@ const rootReducer = combineReducers({
socialAccounts: userSocialsReducer,
taggUsers: taggUsersReducer,
blocked: userBlockReducer,
+ momentCategories: momentCategoriesReducer,
userX: userXReducer,
});
diff --git a/src/types/types.ts b/src/types/types.ts
index e25d1ca7..25160d34 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -112,6 +112,7 @@ export interface UserXType {
following: ProfilePreviewType[];
moments: MomentType[];
socialAccounts: Record;
+ momentCategories: Record;
user: UserType;
profile: ProfileType;
avatar: string;
@@ -125,3 +126,48 @@ export enum VerificationScreenType {
Phone,
Password,
}
+
+/**
+ * Default moment categories
+ */
+export type MomentCategoryType =
+ | 'Friends'
+ | 'Adventure'
+ | 'Photo Dump'
+ | 'Food'
+ | 'Music'
+ | 'Art'
+ | 'Sports'
+ | 'Fashion'
+ | 'Travel'
+ | 'Pets'
+ | 'Nightlife'
+ | 'DIY'
+ | 'Nature'
+ | 'Early Life'
+ | 'Beauty';
+
+/**
+ * Two types for category selection screen
+ */
+export enum CategorySelectionScreenType {
+ Onboarding,
+ Profile,
+}
+
+/**
+ * Gradient type to accomodate new g background gradients for Tagg
+ */
+export enum BackgroundGradientType {
+ Light,
+ Dark,
+}
+
+/**
+ * Linked List style type to accomodate for reusable TaggPopup for displaying popups or running a tutorial
+ */
+export type TaggPopupType = {
+ messageHeader: string;
+ messageBody: string;
+ next?: TaggPopupType;
+};
diff --git a/src/utils/users.ts b/src/utils/users.ts
index 0ed490c7..4f93347d 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -1,3 +1,4 @@
+import {loadUserMomentCategories} from './../store/actions/momentCategories';
import {loadUserX} from './../store/actions/userX';
import {RootState} from './../store/rootReducer';
import AsyncStorage from '@react-native-community/async-storage';
@@ -20,6 +21,7 @@ const loadData = async (dispatch: AppDispatch, user: UserType) => {
await Promise.all([
dispatch(loadUserData(user)),
dispatch(loadFollowData(user.userId)),
+ dispatch(loadUserMomentCategories(user.userId)),
dispatch(loadUserMoments(user.userId)),
dispatch(loadAllSocials(user.userId)),
dispatch(loadBlockedList(user.userId)),
--
cgit v1.2.3-70-g09d2