aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/icons/findFriends/find-friends-blue-icon.svg1
-rw-r--r--src/assets/icons/invite-friends-prompt-icon.pngbin0 -> 153479 bytes
-rw-r--r--src/components/common/TaggPrompt.tsx4
-rw-r--r--src/components/friends/InviteFriendTile.tsx135
-rw-r--r--src/components/friends/index.ts1
-rw-r--r--src/components/moments/CaptionScreenHeader.tsx7
-rw-r--r--src/components/moments/IndividualMomentTitleBar.tsx8
-rw-r--r--src/components/moments/Moment.tsx22
-rw-r--r--src/components/notifications/NotificationPrompts.tsx58
-rw-r--r--src/components/notifications/index.ts1
-rw-r--r--src/components/profile/Friends.tsx173
-rw-r--r--src/components/search/SearchBar.tsx2
-rw-r--r--src/constants/api.ts4
-rw-r--r--src/constants/strings.ts2
-rw-r--r--src/routes/main/MainStackNavigator.tsx4
-rw-r--r--src/routes/main/MainStackScreen.tsx22
-rw-r--r--src/routes/onboarding/OnboardingStackNavigator.tsx2
-rw-r--r--src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx13
-rw-r--r--src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx46
-rw-r--r--src/routes/suggestedPeopleOnboarding/index.ts2
-rw-r--r--src/screens/badge/BadgeSelection.tsx2
-rw-r--r--src/screens/main/NotificationsScreen.tsx37
-rw-r--r--src/screens/onboarding/InvitationCodeVerification.tsx14
-rw-r--r--src/screens/onboarding/Login.tsx1
-rw-r--r--src/screens/onboarding/OnboardingStepThree.tsx4
-rw-r--r--src/screens/onboarding/OnboardingStepTwo.tsx34
-rw-r--r--src/screens/onboarding/RegistrationTwo.tsx2
-rw-r--r--src/screens/profile/CaptionScreen.tsx6
-rw-r--r--src/screens/profile/FriendsListScreen.tsx15
-rw-r--r--src/screens/profile/InviteFriendsScreen.tsx337
-rw-r--r--src/screens/profile/ProfileScreen.tsx10
-rw-r--r--src/screens/profile/index.ts1
-rw-r--r--src/screens/suggestedPeople/SPBody.tsx2
-rw-r--r--src/screens/suggestedPeople/SuggestedPeopleScreen.tsx170
-rw-r--r--src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx6
-rw-r--r--src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx13
-rw-r--r--src/services/UserFriendsService.ts68
-rw-r--r--src/store/actions/userFriends.ts21
-rw-r--r--src/types/types.ts6
-rw-r--r--src/utils/common.ts19
-rw-r--r--src/utils/friends.ts2
-rw-r--r--src/utils/users.ts2
42 files changed, 934 insertions, 345 deletions
diff --git a/src/assets/icons/findFriends/find-friends-blue-icon.svg b/src/assets/icons/findFriends/find-friends-blue-icon.svg
new file mode 100644
index 00000000..26ca145d
--- /dev/null
+++ b/src/assets/icons/findFriends/find-friends-blue-icon.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216 216"><defs><style>.cls-1{fill:#08e2e2;}</style></defs><path class="cls-1" d="M104.28,135.51a58.59,58.59,0,1,0-8.85,8.86l21.79,21.26,9-8.27ZM58.62,145a46.22,46.22,0,1,1,46.21-46.21A46.23,46.23,0,0,1,58.62,145Z"/><path class="cls-1" d="M216,137.23a7.87,7.87,0,0,1-7.87,7.87H185.18v23a7.81,7.81,0,0,1-15.62,0v-23H146.62a7.87,7.87,0,0,1,0-15.73h22.94v-23a7.81,7.81,0,1,1,15.62,0v23h22.95A7.88,7.88,0,0,1,216,137.23Z"/></svg> \ No newline at end of file
diff --git a/src/assets/icons/invite-friends-prompt-icon.png b/src/assets/icons/invite-friends-prompt-icon.png
new file mode 100644
index 00000000..b9422b9f
--- /dev/null
+++ b/src/assets/icons/invite-friends-prompt-icon.png
Binary files differ
diff --git a/src/components/common/TaggPrompt.tsx b/src/components/common/TaggPrompt.tsx
index 75f3009b..d65e30c6 100644
--- a/src/components/common/TaggPrompt.tsx
+++ b/src/components/common/TaggPrompt.tsx
@@ -7,7 +7,7 @@ import {normalize, SCREEN_HEIGHT} from '../../utils';
type TaggPromptProps = {
messageHeader: string;
messageBody: string | Element;
- logoType: 'plus' | 'tagg';
+ logoType: 'plus' | 'tagg' | 'invite_friends';
hideCloseButton?: boolean;
noPadding?: boolean;
onClose: () => void;
@@ -29,6 +29,8 @@ const TaggPrompt: React.FC<TaggPromptProps> = ({
switch (logoType) {
case 'plus':
return require('../../assets/icons/plus-logo.png');
+ case 'invite_friends':
+ return require('../../assets/icons/invite-friends-prompt-icon.png');
case 'tagg':
default:
return require('../../assets/images/logo-purple.png');
diff --git a/src/components/friends/InviteFriendTile.tsx b/src/components/friends/InviteFriendTile.tsx
new file mode 100644
index 00000000..95ebf16a
--- /dev/null
+++ b/src/components/friends/InviteFriendTile.tsx
@@ -0,0 +1,135 @@
+import React, {useEffect, useState} from 'react';
+import {
+ Alert,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ TouchableWithoutFeedback,
+ View,
+} from 'react-native';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {ERROR_SOMETHING_WENT_WRONG} from '../../constants/strings';
+import {inviteFriendService} from '../../services';
+import {normalize} from '../../utils';
+
+interface InviteFriendTileProps {
+ item: Object;
+}
+
+const InviteFriendTile: React.FC<InviteFriendTileProps> = ({item}) => {
+ const [invited, setInvited] = useState<boolean>(false);
+ const [formatedPhoneNumber, setFormattedPhoneNumber] = useState<string>('');
+ const handleInviteFriend = async () => {
+ const response = await inviteFriendService(
+ item.phoneNumber,
+ item.firstName,
+ item.lastName,
+ );
+ if (response) {
+ setInvited(response);
+ } else {
+ Alert.alert(ERROR_SOMETHING_WENT_WRONG);
+ }
+ };
+
+ useEffect(() => {
+ const formatPhoneNumer = () => {
+ const unformatted_number: string = item.phoneNumber;
+ const part_one: string = unformatted_number.substring(2, 5);
+ const part_two: string = unformatted_number.substring(5, 8);
+ const part_three: string = unformatted_number.substring(
+ 8,
+ unformatted_number.length,
+ );
+ const temp = '(' + part_one + ')' + part_two + '-' + part_three;
+ setFormattedPhoneNumber(temp);
+ };
+ formatPhoneNumer();
+ });
+
+ return (
+ <TouchableWithoutFeedback>
+ <View style={styles.container}>
+ <View style={styles.bodyContainer}>
+ <Text style={styles.label}>
+ {item.firstName + ' ' + item.lastName}
+ </Text>
+ <Text style={styles.phoneNumber}>{formatedPhoneNumber}</Text>
+ </View>
+ <TouchableOpacity
+ disabled={invited}
+ style={[
+ styles.button,
+ invited ? styles.pendingButton : styles.inviteButton,
+ ]}
+ onPress={handleInviteFriend}>
+ <Text
+ style={[
+ styles.buttonTitle,
+ invited ? styles.pendingButtonTitle : styles.inviteButtonTitle,
+ ]}>
+ {invited ? 'Pending' : 'Invite'}
+ </Text>
+ </TouchableOpacity>
+ </View>
+ </TouchableWithoutFeedback>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ height: normalize(42),
+ marginBottom: '5%',
+ },
+ bodyContainer: {
+ flexDirection: 'column',
+ height: normalize(42),
+ justifyContent: 'space-around',
+ },
+ label: {
+ fontWeight: '500',
+ fontSize: normalize(14),
+ },
+ phoneNumber: {
+ fontSize: normalize(12),
+ fontWeight: '400',
+ color: '#6C6C6C',
+ letterSpacing: normalize(0.1),
+ },
+ button: {
+ alignSelf: 'center',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 82,
+ height: 25,
+ borderWidth: 2,
+ borderRadius: 2,
+ padding: 0,
+ borderColor: TAGG_LIGHT_BLUE,
+ },
+ pendingButton: {
+ backgroundColor: TAGG_LIGHT_BLUE,
+ },
+ inviteButton: {
+ backgroundColor: 'transparent',
+ },
+ buttonTitle: {
+ padding: 0,
+ fontSize: normalize(11),
+ fontWeight: '700',
+ lineHeight: normalize(13.13),
+ letterSpacing: normalize(0.6),
+ paddingHorizontal: '3.8%',
+ },
+ pendingButtonTitle: {
+ color: 'white',
+ },
+ inviteButtonTitle: {
+ color: TAGG_LIGHT_BLUE,
+ },
+});
+
+export default InviteFriendTile;
diff --git a/src/components/friends/index.ts b/src/components/friends/index.ts
new file mode 100644
index 00000000..42727784
--- /dev/null
+++ b/src/components/friends/index.ts
@@ -0,0 +1 @@
+export {default as InviteFriendTile} from './InviteFriendTile';
diff --git a/src/components/moments/CaptionScreenHeader.tsx b/src/components/moments/CaptionScreenHeader.tsx
index 46dfddfe..0638c128 100644
--- a/src/components/moments/CaptionScreenHeader.tsx
+++ b/src/components/moments/CaptionScreenHeader.tsx
@@ -21,18 +21,15 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
- height: '5%',
},
headerContainer: {
- position: 'absolute',
- left: '50%',
+ width: '90%',
},
header: {
- position: 'relative',
- right: '50%',
fontSize: 20,
fontWeight: 'bold',
color: 'white',
+ textAlign: 'center',
},
});
export default CaptionScreenHeader;
diff --git a/src/components/moments/IndividualMomentTitleBar.tsx b/src/components/moments/IndividualMomentTitleBar.tsx
index 6cdfe0e8..88e0c308 100644
--- a/src/components/moments/IndividualMomentTitleBar.tsx
+++ b/src/components/moments/IndividualMomentTitleBar.tsx
@@ -18,7 +18,9 @@ const IndividualMomentTitleBar: React.FC<IndividualMomentTitleBarProps> = ({
<TouchableOpacity style={styles.closeButton} onPress={close}>
<CloseIcon height={'100%'} width={'100%'} color={'white'} />
</TouchableOpacity>
- <Text style={styles.header}>{title}</Text>
+ <View style={styles.headerContainer}>
+ <Text style={styles.header}>{title}</Text>
+ </View>
</View>
);
};
@@ -30,6 +32,10 @@ const styles = StyleSheet.create({
justifyContent: 'center',
height: '5%',
},
+ headerContainer: {
+ flexShrink: 1,
+ marginLeft: '11%',
+ },
header: {
color: 'white',
fontSize: normalize(18),
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
index 10cf6070..2ac6aebb 100644
--- a/src/components/moments/Moment.tsx
+++ b/src/components/moments/Moment.tsx
@@ -12,7 +12,7 @@ import PlusIcon from '../../assets/icons/plus_icon-01.svg';
import BigPlusIcon from '../../assets/icons/plus_icon-02.svg';
import UpIcon from '../../assets/icons/up_icon.svg';
import {TAGG_LIGHT_BLUE} from '../../constants';
-import {ERROR_UPLOAD_MOMENT_SHORT} from '../../constants/strings';
+import {ERROR_UPLOAD} from '../../constants/strings';
import {normalize, SCREEN_WIDTH} from '../../utils';
import MomentTile from './MomentTile';
@@ -69,7 +69,7 @@ const Moment: React.FC<MomentProps> = ({
})
.catch((err) => {
if (err.code && err.code !== 'E_PICKER_CANCELLED') {
- Alert.alert(ERROR_UPLOAD_MOMENT_SHORT);
+ Alert.alert(ERROR_UPLOAD);
}
});
};
@@ -81,14 +81,14 @@ const Moment: React.FC<MomentProps> = ({
{title}
</Text>
{!userXId ? (
- <>
+ <View style={{flexDirection: 'row'}}>
{showUpButton && move && (
<UpIcon
width={19}
height={19}
onPress={() => move('up', title)}
color={TAGG_LIGHT_BLUE}
- style={{marginLeft: 5}}
+ style={{marginHorizontal: 4}}
/>
)}
{showDownButton && move && (
@@ -97,31 +97,32 @@ const Moment: React.FC<MomentProps> = ({
height={19}
onPress={() => move('down', title)}
color={TAGG_LIGHT_BLUE}
- style={{marginLeft: 5}}
+ style={{marginHorizontal: 4}}
/>
)}
- </>
+ </View>
) : (
<Fragment />
)}
- <View style={styles.flexer} />
+ {/* <View style={styles.flexer} /> */}
{!userXId ? (
- <>
+ <View style={{marginRight: 8, flexDirection: 'row'}}>
<PlusIcon
width={21}
height={21}
onPress={() => navigateToImagePicker()}
color={TAGG_LIGHT_BLUE}
- style={{marginRight: 10}}
+ style={{marginHorizontal: 4}}
/>
{shouldAllowDeletion && (
<DeleteIcon
onPress={() => handleMomentCategoryDelete(title)}
width={19}
height={19}
+ style={{marginHorizontal: 4}}
/>
)}
- </>
+ </View>
) : (
<React.Fragment />
)}
@@ -171,6 +172,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
titleText: {
+ width: '70%',
fontSize: normalize(16),
fontWeight: 'bold',
color: TAGG_LIGHT_BLUE,
diff --git a/src/components/notifications/NotificationPrompts.tsx b/src/components/notifications/NotificationPrompts.tsx
new file mode 100644
index 00000000..dc27925b
--- /dev/null
+++ b/src/components/notifications/NotificationPrompts.tsx
@@ -0,0 +1,58 @@
+import React, {Fragment} from 'react';
+import {Image, StyleSheet, Text} from 'react-native';
+import {TaggPrompt} from '../common';
+
+export const InviteFriendsPrompt: React.FC = () => {
+ return (
+ <TaggPrompt
+ messageHeader={'Invite Friends To Tagg!'}
+ messageBody={
+ 'A new feature that lets you invite your friends to Tagg. \nClick on your friends list to do so!'
+ }
+ logoType={'invite_friends'}
+ hideCloseButton={true}
+ noPadding={true}
+ onClose={() => {}}
+ />
+ );
+};
+
+interface SPPromptNotificationProps {
+ showSPNotifyPopUp: boolean;
+}
+
+export const SPPromptNotification: React.FC<SPPromptNotificationProps> = ({
+ showSPNotifyPopUp,
+}) => {
+ return showSPNotifyPopUp ? (
+ <TaggPrompt
+ messageHeader={'New Suggested People Page!'}
+ messageBody={
+ <>
+ <Text>
+ A new page where you can discover new profiles. Just press the new{' '}
+ </Text>
+ <Image
+ style={styles.icon}
+ source={require('../../assets/navigationIcons/home.png')}
+ />
+ <Text> button on the tab bar to check it out!</Text>
+ </>
+ }
+ logoType={'tagg'}
+ hideCloseButton={true}
+ noPadding={true}
+ onClose={() => {}}
+ />
+ ) : (
+ <Fragment />
+ );
+};
+
+const styles = StyleSheet.create({
+ icon: {
+ width: 20,
+ height: 20,
+ tintColor: 'grey',
+ },
+});
diff --git a/src/components/notifications/index.ts b/src/components/notifications/index.ts
index 0260ce24..733b56f1 100644
--- a/src/components/notifications/index.ts
+++ b/src/components/notifications/index.ts
@@ -1 +1,2 @@
export {default as Notification} from './Notification';
+export {InviteFriendsPrompt} from './NotificationPrompts';
diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx
index 7c7265c5..ac724ae0 100644
--- a/src/components/profile/Friends.tsx
+++ b/src/components/profile/Friends.tsx
@@ -1,14 +1,23 @@
-import React from 'react';
-import {View, StyleSheet, ScrollView, Text} from 'react-native';
-import {ProfilePreviewType, ScreenType} from '../../types';
-import {ProfilePreview} from '../profile';
-import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {useNavigation} from '@react-navigation/native';
+import React, {useEffect, useState} from 'react';
+import {Alert, Linking, ScrollView, StyleSheet, Text, View} from 'react-native';
+import {checkPermission} from 'react-native-contacts';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {useDispatch, useSelector, useStore} from 'react-redux';
import {TAGG_LIGHT_BLUE} from '../../constants';
-import {RootState} from '../../store/rootReducer';
-import {useDispatch, useStore} from 'react-redux';
-import {handleUnfriend} from '../../utils/friends';
+import {usersFromContactsService} from '../../services';
import {NO_USER} from '../../store/initialStates';
-import {TouchableOpacity} from 'react-native-gesture-handler';
+import {RootState} from '../../store/rootReducer';
+import {ProfilePreviewType, ScreenType} from '../../types';
+import {
+ extractContacts,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+} from '../../utils';
+import {handleAddFriend, handleUnfriend} from '../../utils/friends';
+import {ProfilePreview} from '../profile';
+import FindFriendsBlueIcon from '../../assets/icons/findFriends/find-friends-blue-icon.svg';
interface FriendsProps {
result: Array<ProfilePreviewType>;
@@ -19,14 +28,101 @@ interface FriendsProps {
const Friends: React.FC<FriendsProps> = ({result, screenType, userId}) => {
const state: RootState = useStore().getState();
const dispatch = useDispatch();
-
const {user: loggedInUser = NO_USER} = state;
+ const navigation = useNavigation();
+ const [usersFromContacts, setUsersFromContacts] = useState<
+ ProfilePreviewType[]
+ >([]);
+
+ useEffect(() => {
+ const handleFindFriends = () => {
+ extractContacts().then(async (contacts) => {
+ const permission = await checkPermission();
+ if (permission === 'authorized') {
+ let response = await usersFromContactsService(contacts);
+ await setUsersFromContacts(response.existing_tagg_users);
+ } else {
+ console.log('Authorize access to contacts');
+ }
+ });
+ };
+ handleFindFriends();
+ }, []);
+
+ const UsersFromContacts = () => (
+ <>
+ {usersFromContacts?.splice(0, 2).map((profilePreview) => (
+ <View key={profilePreview.id} style={styles.container}>
+ <View style={styles.friend}>
+ <ProfilePreview
+ {...{profilePreview}}
+ previewType={'Friend'}
+ screenType={screenType}
+ />
+ </View>
+ <TouchableOpacity
+ style={styles.addFriendButton}
+ onPress={() => {
+ handleAddFriend(screenType, profilePreview, dispatch, state).then(
+ (success) => {
+ if (success) {
+ let users = usersFromContacts;
+ setUsersFromContacts(
+ users.filter(
+ (user) => user.username !== profilePreview.username,
+ ),
+ );
+ }
+ },
+ );
+ }}>
+ <Text style={styles.addFriendButtonTitle}>Add Friend</Text>
+ </TouchableOpacity>
+ </View>
+ ))}
+ </>
+ );
return (
<>
- <View style={styles.subheader}>
- {/* <Text style={styles.subheaderText}>Friends</Text> */}
- </View>
+ {loggedInUser.userId === userId && (
+ <View style={styles.subheader}>
+ <View style={styles.addFriendHeaderContainer}>
+ <Text style={[styles.subheaderText]}>Add Friends</Text>
+ <TouchableOpacity
+ style={styles.findFriendsButton}
+ onPress={async () => {
+ const permission = await checkPermission();
+ if (permission === 'authorized') {
+ navigation.navigate('InviteFriendsScreen', {
+ screenType: ScreenType.Profile,
+ });
+ } else {
+ Alert.alert(
+ '"Tagg" Would Like to Access Your Contacts',
+ 'This helps you quickly get in touch with friends on the app and more',
+ [
+ {
+ text: "Don't Allow",
+ style: 'cancel',
+ },
+ {text: 'Allow', onPress: () => Linking.openSettings()},
+ ],
+ );
+ }
+ }}>
+ <FindFriendsBlueIcon width={20} height={20} />
+ <Text style={styles.findFriendsSubheaderText}>
+ Invite Friends
+ </Text>
+ </TouchableOpacity>
+ </View>
+ <UsersFromContacts />
+ </View>
+ )}
+ <Text style={[styles.subheaderText, styles.friendsSubheaderText]}>
+ Friends
+ </Text>
<ScrollView
keyboardShouldPersistTaps={'always'}
style={styles.scrollView}
@@ -43,11 +139,11 @@ const Friends: React.FC<FriendsProps> = ({result, screenType, userId}) => {
</View>
{loggedInUser.userId === userId && (
<TouchableOpacity
- style={styles.button}
+ style={styles.unfriendButton}
onPress={() =>
handleUnfriend(screenType, profilePreview, dispatch, state)
}>
- <Text style={styles.buttonTitle}>Unfriend</Text>
+ <Text style={styles.unfriendButtonTitle}>Unfriend</Text>
</TouchableOpacity>
)}
</View>
@@ -63,12 +159,19 @@ const styles = StyleSheet.create({
alignSelf: 'center',
width: SCREEN_WIDTH * 0.85,
},
+ firstScrollView: {},
scrollViewContent: {
alignSelf: 'center',
paddingBottom: SCREEN_HEIGHT / 7,
width: SCREEN_WIDTH * 0.85,
marginTop: '1%',
},
+ addFriendHeaderContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginBottom: '3%',
+ marginTop: '2%',
+ },
header: {flexDirection: 'row'},
subheader: {
alignSelf: 'center',
@@ -81,6 +184,20 @@ const styles = StyleSheet.create({
fontWeight: '600',
lineHeight: normalize(14.32),
},
+ findFriendsButton: {flexDirection: 'row'},
+ friendsSubheaderText: {
+ alignSelf: 'center',
+ width: SCREEN_WIDTH * 0.85,
+ marginVertical: '1%',
+ marginBottom: '2%',
+ },
+ findFriendsSubheaderText: {
+ marginLeft: '5%',
+ color: '#08E2E2',
+ fontSize: normalize(12),
+ fontWeight: '600',
+ lineHeight: normalize(14.32),
+ },
container: {
alignSelf: 'center',
flexDirection: 'row',
@@ -94,7 +211,7 @@ const styles = StyleSheet.create({
alignSelf: 'center',
height: '100%',
},
- button: {
+ addFriendButton: {
alignSelf: 'center',
justifyContent: 'center',
alignItems: 'center',
@@ -104,9 +221,29 @@ const styles = StyleSheet.create({
borderWidth: 2,
borderRadius: 2,
padding: 0,
- backgroundColor: 'transparent',
+ backgroundColor: TAGG_LIGHT_BLUE,
+ },
+ addFriendButtonTitle: {
+ color: 'white',
+ padding: 0,
+ fontSize: normalize(11),
+ fontWeight: '700',
+ lineHeight: normalize(13.13),
+ letterSpacing: normalize(0.6),
+ paddingHorizontal: '3.8%',
+ },
+ unfriendButton: {
+ alignSelf: 'center',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 82,
+ height: '55%',
+ borderColor: TAGG_LIGHT_BLUE,
+ borderWidth: 2,
+ borderRadius: 2,
+ padding: 0,
},
- buttonTitle: {
+ unfriendButtonTitle: {
color: TAGG_LIGHT_BLUE,
padding: 0,
fontSize: normalize(11),
diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx
index 1d0021ad..62bda77e 100644
--- a/src/components/search/SearchBar.tsx
+++ b/src/components/search/SearchBar.tsx
@@ -70,8 +70,6 @@ const SearchBar: React.FC<SearchBarProps> = ({
// TODO: FIGURE OUT WHY CHANGES IN placeholderId ARE NOT REFLECTED HERE
// my thought: the value is set when the function is defined, so it keeps
// its inital value of -1 forever.
- // console.log(`Previous ID: ${placeholderId}`);
- // console.log(`Next ID: ${nextId}`);
setPlaceholderId(nextId);
};
diff --git a/src/constants/api.ts b/src/constants/api.ts
index d2d43063..6afdf384 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -34,6 +34,10 @@ export const DISCOVER_ENDPOINT: string = API_URL + 'discover/';
export const SEARCH_BUTTONS_ENDPOPINT: string = DISCOVER_ENDPOINT + 'search_buttons/';
export const WAITLIST_USER_ENDPOINT: string = API_URL + 'waitlist-user/';
export const COMMENT_THREAD_ENDPOINT: string = API_URL + 'reply/';
+export const USERS_FROM_CONTACTS_ENDPOINT: string =
+ API_URL + 'user_contacts/find_friends/';
+export const INVITE_FRIEND_ENDPOINT: string =
+API_URL + 'user_contacts/invite_friend/';
// Suggested People
export const SP_USERS_ENDPOINT: string = API_URL + 'suggested_people/';
diff --git a/src/constants/strings.ts b/src/constants/strings.ts
index f289cfc1..cb442b7b 100644
--- a/src/constants/strings.ts
+++ b/src/constants/strings.ts
@@ -55,7 +55,7 @@ export const MOMENT_DELETED_MSG = 'Moment deleted....Some moments have to go, to
export const NO_NEW_NOTIFICATIONS = 'You have no new notifications';
export const NO_RESULTS_FOUND = 'No Results Found!';
export const SUCCESS_CATEGORY_DELETE = 'Category successfully deleted, but its memory will live on';
-export const SUCCESS_INVITATION_CODE = 'Perfect! You entered a valid invitation code, you are now able to login and explore Tagg!';
+export const SUCCESS_INVITATION_CODE = 'Welcome to Tagg!';
export const SUCCESS_LINK = (str: string) => `Successfully linked ${str} 🎉`;
export const SUCCESS_PIC_UPLOAD = 'Beautiful, the picture was uploaded successfully!';
export const SUCCESS_BADGES_UPDATE = 'Badges updated successfully!'
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index 142249ce..26d9943b 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -84,6 +84,10 @@ export type MainStackParams = {
badge_title: string;
badge_img: string;
};
+ InviteFriendsScreen: {
+ screenType: ScreenType;
+ };
+ SPWelcomeScreen: {};
};
export const MainStack = createStackNavigator<MainStackParams>();
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 35c306e5..8cefd3cc 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -15,6 +15,7 @@ import {
EditProfile,
FriendsListScreen,
IndividualMoment,
+ InviteFriendsScreen,
MomentCommentsScreen,
MomentUploadPromptScreen,
NotificationsScreen,
@@ -24,6 +25,7 @@ import {
SocialMediaTaggs,
SuggestedPeopleScreen,
SuggestedPeopleUploadPictureScreen,
+ SuggestedPeopleWelcomeScreen,
} from '../../screens';
import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import {ScreenType} from '../../types';
@@ -221,6 +223,19 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
}}
/>
<MainStack.Screen
+ name="InviteFriendsScreen"
+ component={InviteFriendsScreen}
+ initialParams={{screenType}}
+ options={{
+ ...headerBarOptions('black', 'Invites'),
+ }}
+ />
+ <MainStack.Screen
+ name="RequestContactsAccess"
+ component={RequestContactsAccess}
+ initialParams={{screenType}}
+ />
+ <MainStack.Screen
name="EditProfile"
component={EditProfile}
options={{
@@ -248,6 +263,13 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
component={MutualBadgeHolders}
options={{...modalStyle}}
/>
+ <MainStack.Screen
+ name="SPWelcomeScreen"
+ component={SuggestedPeopleWelcomeScreen}
+ options={{
+ ...headerBarOptions('white', ''),
+ }}
+ />
</MainStack.Navigator>
);
};
diff --git a/src/routes/onboarding/OnboardingStackNavigator.tsx b/src/routes/onboarding/OnboardingStackNavigator.tsx
index 0cdeecdf..a51a6c86 100644
--- a/src/routes/onboarding/OnboardingStackNavigator.tsx
+++ b/src/routes/onboarding/OnboardingStackNavigator.tsx
@@ -35,7 +35,7 @@ export type OnboardingStackParams = {
PhoneVerification: {firstName: string; lastName: string; phone: string};
OnboardingStepTwo: {firstName: string; lastName: string; phone: string};
OnboardingStepThree: {userId: string; username: string};
- InvitationCodeVerification: {userId: string};
+ InvitationCodeVerification: {userId: string; username: string};
};
export const OnboardingStack = createStackNavigator<OnboardingStackParams>();
diff --git a/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx b/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx
deleted file mode 100644
index 30a83200..00000000
--- a/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import {createStackNavigator} from '@react-navigation/stack';
-
-export type SuggestedPeopleOnboardingStackParams = {
- WelcomeScreen: undefined;
- UploadPicture: {
- editing: boolean;
- };
- BadgeSelection: {
- editing: boolean;
- };
-};
-
-export const SuggestedPeopleOnboardingStack = createStackNavigator<SuggestedPeopleOnboardingStackParams>();
diff --git a/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx b/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx
deleted file mode 100644
index a02e8373..00000000
--- a/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import {SuggestedPeopleOnboardingStack} from './SuggestedPeopleOnboardingStackNavigator';
-import {
- SuggestedPeopleWelcomeScreen,
- SuggestedPeopleUploadPictureScreen,
- BadgeSelection,
-} from '../../screens';
-import {SCREEN_WIDTH} from '../../utils';
-import {headerBarOptions} from '../main';
-
-const SuggestedPeopleOnboardingStackScreen: React.FC = () => {
- return (
- <SuggestedPeopleOnboardingStack.Navigator
- initialRouteName="WelcomeScreen"
- screenOptions={{
- headerShown: false,
- gestureResponseDistance: {horizontal: SCREEN_WIDTH * 0.6},
- }}>
- <SuggestedPeopleOnboardingStack.Screen
- name="WelcomeScreen"
- component={SuggestedPeopleWelcomeScreen}
- options={{
- ...headerBarOptions('white', ''),
- }}
- />
- <SuggestedPeopleOnboardingStack.Screen
- name="UploadPicture"
- component={SuggestedPeopleUploadPictureScreen}
- initialParams={{editing: false}}
- options={{
- ...headerBarOptions('white', ''),
- }}
- />
- <SuggestedPeopleOnboardingStack.Screen
- name="BadgeSelection"
- component={BadgeSelection}
- initialParams={{editing: false}}
- options={{
- ...headerBarOptions('white', ''),
- }}
- />
- </SuggestedPeopleOnboardingStack.Navigator>
- );
-};
-
-export default SuggestedPeopleOnboardingStackScreen;
diff --git a/src/routes/suggestedPeopleOnboarding/index.ts b/src/routes/suggestedPeopleOnboarding/index.ts
deleted file mode 100644
index df711493..00000000
--- a/src/routes/suggestedPeopleOnboarding/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './SuggestedPeopleOnboardingStackNavigator';
-export * from './SuggestedPeopleOnboardingStackScreen';
diff --git a/src/screens/badge/BadgeSelection.tsx b/src/screens/badge/BadgeSelection.tsx
index cbd7dd88..335d4333 100644
--- a/src/screens/badge/BadgeSelection.tsx
+++ b/src/screens/badge/BadgeSelection.tsx
@@ -74,9 +74,11 @@ const BadgeSelection: React.FC<BadgeSelectionProps> = ({route}) => {
const success = await addBadgesService(selectedBadges);
if (success) {
dispatch(suggestedPeopleBadgesFinished());
+ navigation.navigate('SuggestedPeople');
}
} else {
dispatch(suggestedPeopleBadgesFinished());
+ navigation.navigate('SuggestedPeople');
}
}
}}>
diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx
index 501c44fc..68437f2b 100644
--- a/src/screens/main/NotificationsScreen.tsx
+++ b/src/screens/main/NotificationsScreen.tsx
@@ -21,7 +21,10 @@ import {TouchableOpacity} from 'react-native-gesture-handler';
import {SafeAreaView} from 'react-native-safe-area-context';
import {useDispatch, useSelector} from 'react-redux';
import {TabsGradient, TaggPrompt} from '../../components';
-import {Notification} from '../../components/notifications';
+import {
+ InviteFriendsPrompt,
+ Notification,
+} from '../../components/notifications';
import {
loadUserNotifications,
updateNewNotificationReceived,
@@ -252,30 +255,6 @@ const NotificationsScreen: React.FC = () => {
return null;
};
- const SPPromptNotification: ReactElement = showSPNotifyPopUp ? (
- <TaggPrompt
- messageHeader={'New Suggested People Page!'}
- messageBody={
- <>
- <Text>
- A new page where you can discover new profiles. Just press the new{' '}
- </Text>
- <Image
- style={styles.icon}
- source={require('../../assets/navigationIcons/home.png')}
- />
- <Text> button on the tab bar to check it out!</Text>
- </>
- }
- logoType={'tagg'}
- hideCloseButton={true}
- noPadding={true}
- onClose={() => {}}
- />
- ) : (
- <Fragment />
- );
-
return (
<View style={styles.background}>
<SafeAreaView>
@@ -285,12 +264,13 @@ const NotificationsScreen: React.FC = () => {
</View>
<SectionList
contentContainerStyle={styles.container}
+ stickySectionHeadersEnabled={false}
sections={sectionedNotifications}
keyExtractor={(_item, index) => index.toString()}
renderItem={renderNotification}
renderSectionHeader={renderSectionHeader}
renderSectionFooter={renderSectionFooter}
- ListHeaderComponent={SPPromptNotification}
+ ListHeaderComponent={<InviteFriendsPrompt />}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
@@ -358,11 +338,6 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
},
- icon: {
- width: 20,
- height: 20,
- tintColor: 'grey',
- },
});
export default NotificationsScreen;
diff --git a/src/screens/onboarding/InvitationCodeVerification.tsx b/src/screens/onboarding/InvitationCodeVerification.tsx
index 41d17f29..7cd4b3bf 100644
--- a/src/screens/onboarding/InvitationCodeVerification.tsx
+++ b/src/screens/onboarding/InvitationCodeVerification.tsx
@@ -1,3 +1,4 @@
+import AsyncStorage from '@react-native-community/async-storage';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import React from 'react';
@@ -9,6 +10,7 @@ import {
useBlurOnFulfill,
useClearByFocusCell,
} from 'react-native-confirmation-code-field';
+import {useDispatch} from 'react-redux';
import {
ArrowButton,
Background,
@@ -25,7 +27,7 @@ import {
} from '../../constants/strings';
import {OnboardingStackParams} from '../../routes';
import {BackgroundGradientType} from '../../types';
-import {SCREEN_WIDTH} from '../../utils';
+import {SCREEN_WIDTH, userLogin} from '../../utils';
type InvitationCodeVerificationRouteProp = RouteProp<
OnboardingStackParams,
@@ -56,6 +58,7 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({
value,
setValue,
});
+ const dispatch = useDispatch();
const handleInvitationCodeVerification = async () => {
if (value.length === 6) {
@@ -71,10 +74,11 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({
);
if (verifyInviteCodeResponse.status === 200) {
- navigation.navigate('Login');
- setTimeout(() => {
- Alert.alert(SUCCESS_INVITATION_CODE);
- }, 500);
+ const userId = route.params.userId;
+ const username = route.params.username;
+ await AsyncStorage.setItem('userId', userId);
+ await AsyncStorage.setItem('username', username);
+ userLogin(dispatch, {userId, username});
} else {
Alert.alert(ERROR_INVALID_INVITATION_CODE);
}
diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx
index cfa39dbd..6d9d3a97 100644
--- a/src/screens/onboarding/Login.tsx
+++ b/src/screens/onboarding/Login.tsx
@@ -170,6 +170,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
} else if (statusCode === 200 && !data.isOnboarded) {
navigation.navigate('InvitationCodeVerification', {
userId: data.UserID,
+ username: username,
});
setTimeout(() => {
Alert.alert(ERROR_NOT_ONBOARDED);
diff --git a/src/screens/onboarding/OnboardingStepThree.tsx b/src/screens/onboarding/OnboardingStepThree.tsx
index 64a2a2f7..f22d720f 100644
--- a/src/screens/onboarding/OnboardingStepThree.tsx
+++ b/src/screens/onboarding/OnboardingStepThree.tsx
@@ -57,7 +57,7 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
route,
navigation,
}) => {
- const {userId} = route.params;
+ const {userId, username} = route.params;
let emptyDate: string | undefined;
const [form, setForm] = React.useState({
smallPic: '',
@@ -224,12 +224,12 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
},
body: request,
});
- console.log(route.params.userId);
let statusCode = response.status;
let data = await response.json();
if (statusCode === 200) {
navigation.navigate('InvitationCodeVerification', {
userId: route.params.userId,
+ username: username,
});
} else if (statusCode === 400) {
Alert.alert(
diff --git a/src/screens/onboarding/OnboardingStepTwo.tsx b/src/screens/onboarding/OnboardingStepTwo.tsx
index 93342c3f..e79e1886 100644
--- a/src/screens/onboarding/OnboardingStepTwo.tsx
+++ b/src/screens/onboarding/OnboardingStepTwo.tsx
@@ -245,23 +245,6 @@ const OnboardingStepTwo: React.FC<OnboardingStepTwoProps> = ({
<Text style={styles.formHeader}>SIGN UP</Text>
</View>
<TaggInput
- accessibilityHint="Enter your email."
- accessibilityLabel="Email input field."
- placeholder="Email"
- autoCompleteType="email"
- textContentType="emailAddress"
- autoCapitalize="none"
- returnKeyType="next"
- keyboardType="email-address"
- onChangeText={handleEmailUpdate}
- blurOnSubmit={false}
- ref={emailRef}
- valid={form.isValidEmail}
- invalidWarning={'Please enter a valid email address.'}
- attemptedSubmit={form.attemptedSubmit}
- width={280}
- />
- <TaggInput
accessibilityHint="Enter a username."
accessibilityLabel="Username input field."
placeholder="Username"
@@ -281,6 +264,23 @@ const OnboardingStepTwo: React.FC<OnboardingStepTwoProps> = ({
width={280}
/>
<TaggInput
+ accessibilityHint="Enter your email."
+ accessibilityLabel="Email input field."
+ placeholder="School Email"
+ autoCompleteType="email"
+ textContentType="emailAddress"
+ autoCapitalize="none"
+ returnKeyType="next"
+ keyboardType="email-address"
+ onChangeText={handleEmailUpdate}
+ blurOnSubmit={false}
+ ref={emailRef}
+ valid={form.isValidEmail}
+ invalidWarning={'Please enter a valid email address.'}
+ attemptedSubmit={form.attemptedSubmit}
+ width={280}
+ />
+ <TaggInput
accessibilityHint="Enter a password."
accessibilityLabel="Password input field."
placeholder="Password"
diff --git a/src/screens/onboarding/RegistrationTwo.tsx b/src/screens/onboarding/RegistrationTwo.tsx
index 707e621a..6d7b2226 100644
--- a/src/screens/onboarding/RegistrationTwo.tsx
+++ b/src/screens/onboarding/RegistrationTwo.tsx
@@ -214,7 +214,7 @@ const RegistrationTwo: React.FC<RegistrationTwoProps> = ({
<TaggInput
accessibilityHint="Enter your email."
accessibilityLabel="Email input field."
- placeholder="Email"
+ placeholder="School Email"
autoCompleteType="email"
textContentType="emailAddress"
autoCapitalize="none"
diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx
index 01e859ba..998897e2 100644
--- a/src/screens/profile/CaptionScreen.tsx
+++ b/src/screens/profile/CaptionScreen.tsx
@@ -152,10 +152,8 @@ const styles = StyleSheet.create({
position: 'relative',
backgroundColor: 'white',
width: '100%',
- paddingLeft: '2%',
- paddingRight: '2%',
- paddingBottom: '1%',
- paddingTop: '1%',
+ paddingHorizontal: '2%',
+ paddingVertical: '1%',
height: 60,
},
});
diff --git a/src/screens/profile/FriendsListScreen.tsx b/src/screens/profile/FriendsListScreen.tsx
index 1cfef058..886ab9c4 100644
--- a/src/screens/profile/FriendsListScreen.tsx
+++ b/src/screens/profile/FriendsListScreen.tsx
@@ -1,6 +1,12 @@
import {RouteProp} from '@react-navigation/native';
import React from 'react';
-import {SafeAreaView, StyleSheet, View} from 'react-native';
+import {
+ SafeAreaView,
+ ScrollView,
+ StatusBar,
+ StyleSheet,
+ View,
+} from 'react-native';
import {useSelector} from 'react-redux';
import {Friends, TabsGradient} from '../../components';
import {MainStackParams} from '../../routes';
@@ -25,9 +31,10 @@ const FriendsListScreen: React.FC<FriendsListScreenProps> = ({route}) => {
return (
<>
<SafeAreaView>
- <View style={styles.body}>
+ <StatusBar barStyle="dark-content" />
+ <ScrollView style={styles.body}>
<Friends result={friends} screenType={screenType} userId={userXId} />
- </View>
+ </ScrollView>
</SafeAreaView>
<TabsGradient />
</>
@@ -45,7 +52,7 @@ const styles = StyleSheet.create({
body: {
marginTop: HeaderHeight,
width: SCREEN_WIDTH,
- height: SCREEN_HEIGHT * 0.8,
+ height: SCREEN_HEIGHT - HeaderHeight,
},
});
diff --git a/src/screens/profile/InviteFriendsScreen.tsx b/src/screens/profile/InviteFriendsScreen.tsx
new file mode 100644
index 00000000..4af52349
--- /dev/null
+++ b/src/screens/profile/InviteFriendsScreen.tsx
@@ -0,0 +1,337 @@
+import React, {useEffect, useState} from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ SafeAreaView,
+ StyleSheet,
+ TextInput,
+ FlatList,
+ Keyboard,
+ Linking,
+ StatusBar,
+ TouchableWithoutFeedback,
+ ScrollView,
+} from 'react-native';
+import {useDispatch, useStore} from 'react-redux';
+import {ProfilePreviewType} from '../../types';
+import {
+ extractContacts,
+ handleAddFriend,
+ HeaderHeight,
+ isIPhoneX,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+ StatusBarHeight,
+} from '../../utils';
+import {checkPermission} from 'react-native-contacts';
+import {usersFromContactsService} from '../../services/UserFriendsService';
+import {ProfilePreview, TabsGradient} from '../../components';
+import Animated from 'react-native-reanimated';
+import Icon from 'react-native-vector-icons/Feather';
+import {InviteFriendTile} from '../../components/friends';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {MainStackParams} from '../../routes';
+import {RouteProp} from '@react-navigation/native';
+const AnimatedIcon = Animated.createAnimatedComponent(Icon);
+
+type InviteFriendsScreenRouteProp = RouteProp<
+ MainStackParams,
+ 'InviteFriendsScreen'
+>;
+
+interface InviteFriendsScreenProps {
+ route: InviteFriendsScreenRouteProp;
+}
+
+const InviteFriendsScreen: React.FC<InviteFriendsScreenProps> = ({route}) => {
+ const {screenType} = route.params;
+ const dispatch = useDispatch();
+ const state = useStore().getState();
+ const [usersFromContacts, setUsersFromContacts] = useState<
+ ProfilePreviewType[]
+ >([]);
+ const [nonUsersFromContacts, setNonUsersFromContacts] = useState<[]>([]);
+ type SearchResultType = {
+ usersFromContacts: ProfilePreviewType[];
+ nonUsersFromContacts: [];
+ };
+ const [results, setResults] = useState<SearchResultType>({
+ usersFromContacts: usersFromContacts,
+ nonUsersFromContacts: nonUsersFromContacts,
+ });
+ const [query, setQuery] = useState('');
+
+ useEffect(() => {
+ const handleFindFriends = () => {
+ extractContacts().then(async (retrievedContacts) => {
+ const permission = await checkPermission();
+ if (permission === 'authorized') {
+ let response = await usersFromContactsService(retrievedContacts);
+ await setUsersFromContacts(response.existing_tagg_users);
+ await setNonUsersFromContacts(response.invite_from_contacts);
+ setResults({
+ usersFromContacts: response.existing_tagg_users,
+ nonUsersFromContacts: response.invite_from_contacts,
+ });
+ } else {
+ Linking.openSettings();
+ }
+ });
+ };
+ handleFindFriends();
+ }, []);
+
+ /*
+ * Main handler for changes in query.
+ */
+ useEffect(() => {
+ const search = async () => {
+ if (query.length > 0) {
+ const searchResultsUsers = usersFromContacts.filter(
+ (item: ProfilePreviewType) =>
+ (item.first_name + ' ' + item.last_name)
+ .toLowerCase()
+ .startsWith(query) ||
+ item.username.toLowerCase().startsWith(query) ||
+ item.last_name.toLowerCase().startsWith(query),
+ );
+ const searchResultsNonUsers = nonUsersFromContacts.filter(
+ (item) =>
+ (item.firstName + ' ' + item.lastName)
+ .toLowerCase()
+ .startsWith(query) ||
+ item.lastName.toLowerCase().startsWith(query),
+ );
+ const sanitizedResult = {
+ usersFromContacts: searchResultsUsers,
+ nonUsersFromContacts: searchResultsNonUsers,
+ };
+ setResults(sanitizedResult);
+ } else {
+ setResults({
+ usersFromContacts: usersFromContacts,
+ nonUsersFromContacts: nonUsersFromContacts,
+ });
+ }
+ };
+ search();
+ }, [query]);
+
+ const UsersFromContacts = () => (
+ <>
+ <FlatList
+ showsVerticalScrollIndicator={false}
+ scrollEnabled={false}
+ data={results.usersFromContacts}
+ keyExtractor={(item) => item.username}
+ renderItem={({item}) => (
+ <View key={item.id} style={styles.ppContainer}>
+ <View style={styles.friend}>
+ <ProfilePreview
+ {...{profilePreview: item}}
+ previewType={'Friend'}
+ screenType={screenType}
+ />
+ </View>
+ <TouchableOpacity
+ style={styles.addFriendButton}
+ onPress={() => {
+ handleAddFriend(screenType, item, dispatch, state).then(
+ (success) => {
+ if (success) {
+ let users = usersFromContacts;
+ const filteredUsers = users.filter(
+ (user) => user.username !== item.username,
+ );
+ setResults({
+ ...results,
+ usersFromContacts: filteredUsers,
+ });
+ }
+ },
+ );
+ }}>
+ <Text style={styles.addFriendButtonTitle}>Add Friend</Text>
+ </TouchableOpacity>
+ </View>
+ )}
+ />
+ </>
+ );
+
+ return (
+ <View style={styles.mainContainer}>
+ <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
+ <SafeAreaView style={{marginTop: HeaderHeight + StatusBarHeight}}>
+ <StatusBar barStyle="dark-content" />
+ <ScrollView
+ style={styles.body}
+ contentContainerStyle={{paddingBottom: SCREEN_HEIGHT * 0.1}}>
+ <View style={styles.headerContainer}>
+ <Text style={styles.headerText}>
+ Sharing is caring, invite friends, and create moments together!
+ </Text>
+ </View>
+ <View style={styles.container}>
+ <Animated.View style={styles.inputContainer}>
+ <AnimatedIcon
+ name="search"
+ color={'#7E7E7E'}
+ size={16}
+ style={styles.searchIcon}
+ />
+ <TextInput
+ style={[styles.input]}
+ placeholderTextColor={'#828282'}
+ clearButtonMode="while-editing"
+ autoCapitalize="none"
+ autoCorrect={false}
+ onChangeText={(text) => {
+ setQuery(text.toLowerCase());
+ }}
+ onBlur={() => {
+ Keyboard.dismiss();
+ }}
+ onEndEditing={() => {
+ Keyboard.dismiss();
+ }}
+ value={query}
+ placeholder={'Search'}
+ />
+ </Animated.View>
+ </View>
+ <View style={styles.subheader}>
+ <Text style={styles.subheaderText}>Add Friends</Text>
+ <UsersFromContacts />
+ </View>
+ <View style={styles.subheader}>
+ <Text style={styles.subheaderText}>Invite your friends!</Text>
+ <FlatList
+ contentContainerStyle={styles.nonUsersFlatListContainer}
+ showsVerticalScrollIndicator={false}
+ scrollEnabled={false}
+ data={results.nonUsersFromContacts}
+ keyExtractor={(item) => item.phoneNumber}
+ renderItem={({item}) => <InviteFriendTile item={item} />}
+ />
+ </View>
+ </ScrollView>
+ </SafeAreaView>
+ </TouchableWithoutFeedback>
+ <TabsGradient />
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ mainContainer: {backgroundColor: 'white', height: SCREEN_HEIGHT},
+ body: {
+ paddingTop: 10,
+ height: SCREEN_HEIGHT,
+ backgroundColor: '#fff',
+ },
+ headerContainer: {
+ width: SCREEN_WIDTH * 0.85,
+ height: isIPhoneX() ? SCREEN_HEIGHT * 0.06 : SCREEN_HEIGHT * 0.08,
+ alignSelf: 'center',
+ },
+ nonUsersFlatListContainer: {paddingBottom: 130},
+ subheader: {
+ alignSelf: 'center',
+ width: SCREEN_WIDTH * 0.85,
+ marginBottom: '5%',
+ },
+ subheaderText: {
+ color: '#828282',
+ fontSize: normalize(12),
+ fontWeight: '600',
+ lineHeight: normalize(14.32),
+ marginBottom: '5%',
+ },
+ headerText: {
+ textAlign: 'center',
+ color: '#828282',
+ fontSize: normalize(12),
+ fontWeight: '600',
+ lineHeight: normalize(14.32),
+ marginBottom: '5%',
+ },
+ container: {
+ alignSelf: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '100%',
+ height: normalize(42),
+ alignItems: 'center',
+ marginBottom: '3%',
+ marginHorizontal: 10,
+ },
+ ppContainer: {
+ alignSelf: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '100%',
+ height: normalize(42),
+ alignItems: 'center',
+ marginBottom: '5%',
+ marginHorizontal: 10,
+ },
+ inputContainer: {
+ flexGrow: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 8,
+ marginHorizontal: '3%',
+ borderRadius: 20,
+ backgroundColor: '#F0F0F0',
+ height: 34,
+ },
+ searchIcon: {
+ marginRight: '5%',
+ },
+ input: {
+ flex: 1,
+ fontSize: normalize(16),
+ color: '#000',
+ letterSpacing: normalize(0.5),
+ },
+ cancelButton: {
+ height: '100%',
+ position: 'absolute',
+ justifyContent: 'center',
+ paddingHorizontal: 8,
+ },
+ cancelText: {
+ color: '#818181',
+ fontWeight: '500',
+ },
+ friend: {
+ alignSelf: 'center',
+ height: '100%',
+ },
+ addFriendButton: {
+ alignSelf: 'center',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: 82,
+ height: 25,
+ borderColor: TAGG_LIGHT_BLUE,
+ borderWidth: 2,
+ borderRadius: 2,
+ padding: 0,
+ backgroundColor: TAGG_LIGHT_BLUE,
+ },
+ addFriendButtonTitle: {
+ color: 'white',
+ padding: 0,
+ fontSize: normalize(11),
+ fontWeight: '700',
+ lineHeight: normalize(13.13),
+ letterSpacing: normalize(0.6),
+ paddingHorizontal: '3.8%',
+ },
+});
+
+export default InviteFriendsScreen;
diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx
index 5edc6277..313e2f2c 100644
--- a/src/screens/profile/ProfileScreen.tsx
+++ b/src/screens/profile/ProfileScreen.tsx
@@ -38,11 +38,11 @@ const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => {
* This is done to reset the users stored in our store for the Search screen.
* Read more about useFocusEffect here : https://reactnavigation.org/docs/function-after-focusing-screen/
*/
- useFocusEffect(() => {
- if (!userXId) {
- dispatch(resetScreenType(screenType));
- }
- });
+ // useFocusEffect(() => {
+ // if (!userXId) {
+ // dispatch(resetScreenType(screenType));
+ // }
+ // });
return (
<>
diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts
index 9d651729..f74946a6 100644
--- a/src/screens/profile/index.ts
+++ b/src/screens/profile/index.ts
@@ -6,3 +6,4 @@ export {default as MomentCommentsScreen} from './MomentCommentsScreen';
export {default as FriendsListScreen} from './FriendsListScreen';
export {default as EditProfile} from './EditProfile';
export {default as MomentUploadPromptScreen} from './MomentUploadPromptScreen';
+export {default as InviteFriendsScreen} from './InviteFriendsScreen';
diff --git a/src/screens/suggestedPeople/SPBody.tsx b/src/screens/suggestedPeople/SPBody.tsx
index 06d3efb3..8e0801c2 100644
--- a/src/screens/suggestedPeople/SPBody.tsx
+++ b/src/screens/suggestedPeople/SPBody.tsx
@@ -121,7 +121,7 @@ const SPBody: React.FC<SPBodyProps> = ({
<TouchableOpacity
onPress={() => {
navigation.push('Profile', {
- userXId: user.id,
+ userXId: loggedInUserId === user.id ? undefined : user.id,
screenType,
});
}}
diff --git a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
index 4094b0a3..76889657 100644
--- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
+++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
@@ -1,37 +1,31 @@
import {useFocusEffect, useNavigation} from '@react-navigation/native';
-import React, {useCallback, useEffect, useState, useRef} from 'react';
-import {
- FlatList,
- RefreshControl,
- StatusBar,
- StyleSheet,
- ViewToken,
-} from 'react-native';
-
+import React, {useCallback, useEffect, useRef, useState} from 'react';
+import {FlatList, RefreshControl, StatusBar, ViewToken} from 'react-native';
import {useDispatch, useSelector, useStore} from 'react-redux';
-import {TabsGradient, TaggLoadingIndicator, Background} from '../../components';
+import {Background, TabsGradient, TaggLoadingIndicator} from '../../components';
import {SP_PAGE_SIZE} from '../../constants';
+import {MainStack} from '../../routes';
+import MainStackScreen from '../../routes/main/MainStackScreen';
import SuggestedPeopleOnboardingStackScreen from '../../routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen';
import {getSuggestedPeople} from '../../services/SuggestedPeopleService';
import {cancelFriendRequest, resetScreenType} from '../../store/actions';
import {RootState} from '../../store/rootReducer';
import {
+ BackgroundGradientType,
FriendshipStatusType,
ProfilePreviewType,
ScreenType,
SuggestedPeopleDataType,
- BackgroundGradientType,
} from '../../types';
import {
fetchUserX,
getUserAsProfilePreviewType,
handleAddFriend,
- normalize,
- SCREEN_HEIGHT,
- SCREEN_WIDTH,
} from '../../utils';
+import {SuggestedPeopleWelcomeScreen} from '../suggestedPeopleOnboarding';
import {userXInStore} from './../../utils/';
import SPBody from './SPBody';
+
/**
* Bare bones for suggested people consisting of:
* * Image, title, name, username, add friend button [w/o functionality]
@@ -209,9 +203,13 @@ const SuggestedPeopleScreen: React.FC = () => {
[],
);
- return suggested_people_linked === 0 ? (
- <SuggestedPeopleOnboardingStackScreen />
- ) : loading ? (
+ useFocusEffect(() => {
+ if (suggested_people_linked === 0) {
+ navigation.navigate('SPWelcomeScreen');
+ }
+ });
+
+ return loading ? (
<>
<TaggLoadingIndicator fullscreen />
<Background gradientType={BackgroundGradientType.Dark} />
@@ -245,142 +243,4 @@ const SuggestedPeopleScreen: React.FC = () => {
);
};
-const styles = StyleSheet.create({
- mainContainer: {
- flexDirection: 'column',
- width: SCREEN_WIDTH,
- height: SCREEN_HEIGHT,
- paddingVertical: '15%',
- paddingBottom: '20%',
- justifyContent: 'space-between',
- alignSelf: 'center',
- },
- marginManager: {marginHorizontal: '5%'},
- image: {
- position: 'absolute',
- width: SCREEN_WIDTH,
- height: SCREEN_HEIGHT,
- zIndex: 0,
- },
- title: {
- zIndex: 1,
- paddingTop: '3%',
- alignSelf: 'center',
- fontSize: normalize(22),
- lineHeight: normalize(26),
- fontWeight: '800',
- letterSpacing: normalize(3),
- color: '#FFFEFE',
- textShadowColor: 'rgba(0, 0, 0, 0.4)',
- textShadowOffset: {width: normalize(2), height: normalize(2)},
- textShadowRadius: normalize(2),
- },
- firstName: {
- color: '#fff',
- fontWeight: '800',
- fontSize: normalize(24),
- lineHeight: normalize(29),
- textShadowColor: 'rgba(0, 0, 0, 0.3)',
- textShadowOffset: {width: normalize(2), height: normalize(2)},
- textShadowRadius: normalize(2),
- letterSpacing: normalize(2.5),
- alignSelf: 'baseline',
- },
- username: {
- color: '#fff',
- fontWeight: '600',
- fontSize: normalize(15),
- lineHeight: normalize(18),
- textShadowColor: 'rgba(0, 0, 0, 0.3)',
- textShadowOffset: {width: normalize(2), height: normalize(2)},
- textShadowRadius: normalize(2),
- letterSpacing: normalize(2),
- },
- nameInfoContainer: {},
- addButton: {
- justifyContent: 'center',
- alignItems: 'center',
- width: SCREEN_WIDTH * 0.3,
- height: SCREEN_WIDTH * 0.085,
- padding: 0,
- borderWidth: 2,
- borderColor: '#fff',
- borderRadius: 1,
- marginLeft: '1%',
- marginTop: '4%',
- shadowColor: 'rgb(0, 0, 0)',
- shadowRadius: 2,
- shadowOffset: {width: 2, height: 2},
- shadowOpacity: 0.5,
- },
- addButtonTitle: {
- color: 'white',
- padding: 0,
- fontSize: normalize(15),
- lineHeight: normalize(18),
- fontWeight: 'bold',
- textAlign: 'center',
- letterSpacing: normalize(1),
- },
- addUserContainer: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'flex-start',
- marginBottom: '5%',
- },
- requestedButton: {
- justifyContent: 'center',
- alignItems: 'center',
- width: SCREEN_WIDTH * 0.3,
- height: SCREEN_WIDTH * 0.085,
- padding: 0,
- borderWidth: 2,
- borderColor: 'transparent',
- borderRadius: 1,
- marginLeft: '1%',
- marginTop: '4%',
- shadowColor: 'rgb(0, 0, 0)',
- shadowRadius: 2,
- shadowOffset: {width: 2, height: 2},
- shadowOpacity: 0.5,
- },
- requestedButtonTitle: {
- backgroundColor: 'transparent',
- fontSize: normalize(15),
- lineHeight: normalize(18),
- fontWeight: 'bold',
- textAlign: 'center',
- letterSpacing: normalize(1),
- },
- body: {},
-
- button: {
- justifyContent: 'center',
- alignItems: 'center',
- width: SCREEN_WIDTH * 0.4,
- aspectRatio: 154 / 33,
- borderWidth: 2,
- borderColor: '#fff',
- borderRadius: 3,
- marginRight: '2%',
- marginLeft: '1%',
- },
- transparentBG: {
- backgroundColor: 'transparent',
- },
- lightBlueBG: {
- backgroundColor: '#fff',
- },
- label: {
- fontSize: normalize(15),
- fontWeight: '700',
- letterSpacing: 1,
- },
- blueLabel: {
- color: '#fff',
- },
- whiteLabel: {
- color: 'white',
- },
-});
export default SuggestedPeopleScreen;
diff --git a/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx
index 87e22d9e..0a4e5718 100644
--- a/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx
+++ b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx
@@ -98,7 +98,7 @@ const SuggestedPeopleUploadPictureScreen: React.FC<SuggestedPeopleUploadPictureS
if (success) {
dispatch(uploadedSuggestedPeoplePhoto(image));
if (!editing) {
- navigation.push('BadgeSelection', {editing: false});
+ navigation.navigate('BadgeSelection', {editing: false});
}
} else {
Alert.alert(ERROR_UPLOAD);
@@ -106,12 +106,12 @@ const SuggestedPeopleUploadPictureScreen: React.FC<SuggestedPeopleUploadPictureS
setLoading(false);
// Navigated back to Profile if user is editing their Suggested People Picture
if (editing) {
+ navigation.goBack();
setTimeout(() => {
Alert.alert(success ? SUCCESS_PIC_UPLOAD : ERROR_UPLOAD_SP_PHOTO);
}, 500);
}
}
- navigation.goBack();
};
return (
@@ -161,7 +161,7 @@ const SuggestedPeopleUploadPictureScreen: React.FC<SuggestedPeopleUploadPictureS
{editing && (
<TouchableOpacity
onPress={() => {
- navigation.push('BadgeSelection', {editing: true});
+ navigation.navigate('BadgeSelection', {editing: true});
}}>
<View style={styles.editBadgesMainContainer}>
<View style={styles.editBadgesSubContainer}>
diff --git a/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx
index 10f3b3a5..2f12d909 100644
--- a/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx
+++ b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx
@@ -1,6 +1,6 @@
import {BlurView} from '@react-native-community/blur';
import {useNavigation} from '@react-navigation/native';
-import React from 'react';
+import React, {Fragment, useEffect} from 'react';
import {Image, StatusBar, StyleSheet, TouchableOpacity} from 'react-native';
import {Text} from 'react-native-animatable';
import {SafeAreaView} from 'react-native-safe-area-context';
@@ -9,6 +9,13 @@ import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
const SuggestedPeopleWelcomeScreen: React.FC = () => {
const navigation = useNavigation();
+
+ useEffect(() => {
+ navigation.setOptions({
+ headerBackImage: () => <Fragment />,
+ });
+ }, []);
+
return (
<>
<StatusBar barStyle={'light-content'} />
@@ -35,7 +42,9 @@ const SuggestedPeopleWelcomeScreen: React.FC = () => {
</BlurView>
<TouchableOpacity
style={styles.nextButton}
- onPress={() => navigation.push('UploadPicture')}>
+ onPress={() =>
+ navigation.navigate('UpdateSPPicture', {editing: false})
+ }>
<UpArrow color={'white'} />
</TouchableOpacity>
</>
diff --git a/src/services/UserFriendsService.ts b/src/services/UserFriendsService.ts
index 5ce9df29..da39380f 100644
--- a/src/services/UserFriendsService.ts
+++ b/src/services/UserFriendsService.ts
@@ -1,9 +1,17 @@
//Abstracted common friends api calls out here
+import AsyncStorage from '@react-native-community/async-storage';
import {Alert} from 'react-native';
-import {FriendshipStatusType} from '../types';
-import {FRIENDS_ENDPOINT} from '../constants';
-import {ERROR_SOMETHING_WENT_WRONG_REFRESH} from '../constants/strings';
+import {ContactType, FriendshipStatusType} from '../types';
+import {
+ FRIENDS_ENDPOINT,
+ INVITE_FRIEND_ENDPOINT,
+ USERS_FROM_CONTACTS_ENDPOINT,
+} from '../constants';
+import {
+ ERROR_SOMETHING_WENT_WRONG,
+ ERROR_SOMETHING_WENT_WRONG_REFRESH,
+} from '../constants/strings';
export const loadFriends = async (userId: string, token: string) => {
try {
@@ -180,3 +188,57 @@ export const deleteFriendshipService = async (
return false;
}
};
+
+export const usersFromContactsService = async (
+ contacts: Array<ContactType>,
+) => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ const response = await fetch(USERS_FROM_CONTACTS_ENDPOINT, {
+ method: 'POST',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ body: JSON.stringify({
+ contacts: contacts,
+ }),
+ });
+ const status = response.status;
+ if (Math.floor(status / 100) === 2) {
+ const users_data = await response.json();
+ return users_data;
+ } else {
+ console.log(
+ 'Something went wrong! 😭',
+ 'Not able to retrieve tagg users list',
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH);
+ return false;
+ }
+};
+
+export const inviteFriendService = async (
+ phoneNumber: string,
+ inviteeFirstName: string,
+ inviteeLastName: string,
+) => {
+ const token = await AsyncStorage.getItem('token');
+ const response = await fetch(INVITE_FRIEND_ENDPOINT, {
+ method: 'POST',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ body: JSON.stringify({
+ invitee_phone_number: phoneNumber,
+ invitee_first_name: inviteeFirstName,
+ invitee_last_name: inviteeLastName,
+ }),
+ });
+ if (response.status === 201 || response.status === 200) {
+ return await response.json();
+ }
+ return false;
+};
diff --git a/src/store/actions/userFriends.ts b/src/store/actions/userFriends.ts
index 4f55acc8..9da3cb4a 100644
--- a/src/store/actions/userFriends.ts
+++ b/src/store/actions/userFriends.ts
@@ -1,4 +1,4 @@
-import {getTokenOrLogout} from '../../utils';
+import {getTokenOrLogout, userXInStore} from '../../utils';
import {RootState} from '../rootReducer';
import {
FriendshipStatusType,
@@ -90,6 +90,7 @@ export const friendUnfriendUser = (
export const addFriend = (
friend: ProfilePreviewType, // userX's profile preview
screenType: ScreenType, //screentype from content
+ state: RootState,
): ThunkAction<
Promise<boolean | undefined>,
RootState,
@@ -100,14 +101,16 @@ export const addFriend = (
const token = await getTokenOrLogout(dispatch);
const success = await addFriendService(friend.id, token);
if (success) {
- dispatch({
- type: userXFriendshipEdited.type,
- payload: {
- userId: friend.id,
- screenType,
- data: 'requested',
- },
- });
+ if (userXInStore(state, screenType, friend.id)) {
+ dispatch({
+ type: userXFriendshipEdited.type,
+ payload: {
+ userId: friend.id,
+ screen: screenType,
+ data: 'requested',
+ },
+ });
+ }
return true;
}
} catch (error) {
diff --git a/src/types/types.ts b/src/types/types.ts
index 8937e74c..692da8b4 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -264,3 +264,9 @@ export type SearchCategoryType = {
name: string;
category: string;
};
+
+export type ContactType = {
+ phone_number: string;
+ first_name: string;
+ last_name: string;
+};
diff --git a/src/utils/common.ts b/src/utils/common.ts
index c1049c42..0a76ec08 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,8 +1,9 @@
-import {NotificationType} from './../types/types';
+import {ContactType, NotificationType} from './../types/types';
import moment from 'moment';
import {Linking} from 'react-native';
import {BROWSABLE_SOCIAL_URLS, TOGGLE_BUTTON_TYPE} from '../constants';
import AsyncStorage from '@react-native-community/async-storage';
+import {getAll} from 'react-native-contacts';
export const getToggleButtonText: (
buttonType: string,
@@ -115,3 +116,19 @@ export const shuffle = (array: any[]) => {
return array;
};
+
+export const extractContacts = async () => {
+ let retrievedContacts: Array<ContactType> = [];
+ await getAll().then((contacts) => {
+ contacts.map((contact) => {
+ contact.phoneNumbers.map((phoneNumber) => {
+ retrievedContacts.push({
+ first_name: contact.givenName,
+ last_name: contact.familyName,
+ phone_number: phoneNumber.number,
+ });
+ });
+ });
+ });
+ return retrievedContacts;
+};
diff --git a/src/utils/friends.ts b/src/utils/friends.ts
index 6e3b645a..5b0ded8f 100644
--- a/src/utils/friends.ts
+++ b/src/utils/friends.ts
@@ -60,7 +60,7 @@ export const handleAddFriend = async (
dispatch: AppDispatch,
state: RootState,
) => {
- const success = await dispatch(addFriend(friend, screenType));
+ const success = await dispatch(addFriend(friend, screenType, state));
await dispatch(updateUserXFriends(friend.id, state));
await dispatch(updateUserXProfileAllScreens(friend.id, state));
return success;
diff --git a/src/utils/users.ts b/src/utils/users.ts
index f54d461c..d5e44b36 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -96,7 +96,7 @@ export const userXInStore = (
userId: string,
) => {
const userX = state.userX[screen];
- return userId in userX && userX[userId].user.userId;
+ return userX && userId in userX && userX[userId].user.userId;
};
/**