aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/universities/brown.pngbin0 -> 3234 bytes
-rw-r--r--src/components/notifications/Notification.tsx2
-rw-r--r--src/components/profile/Content.tsx68
-rw-r--r--src/components/profile/Friends.tsx (renamed from src/components/profile/Followers.tsx)21
-rw-r--r--src/components/profile/FriendsCount.tsx (renamed from src/components/profile/FollowCount.tsx)38
-rw-r--r--src/components/profile/ProfileBody.tsx31
-rw-r--r--src/components/profile/ProfileHeader.tsx63
-rw-r--r--src/components/profile/ProfileMoreInfoDrawer.tsx42
-rw-r--r--src/components/profile/ProfilePreview.tsx26
-rw-r--r--src/components/profile/ToggleButton.tsx23
-rw-r--r--src/components/profile/UniversityIcon.tsx58
-rw-r--r--src/components/profile/index.ts3
-rw-r--r--src/constants/api.ts5
-rw-r--r--src/constants/constants.ts14
-rw-r--r--src/routes/main/MainStackNavigator.tsx3
-rw-r--r--src/routes/main/MainStackScreen.tsx6
-rw-r--r--src/screens/onboarding/ProfileOnboarding.tsx210
-rw-r--r--src/screens/profile/FriendsListScreen.tsx (renamed from src/screens/profile/FollowersListScreen.tsx)36
-rw-r--r--src/screens/profile/index.ts2
-rw-r--r--src/services/UserFollowServices.ts85
-rw-r--r--src/services/UserFriendsServices.ts63
-rw-r--r--src/services/UserProfileService.ts22
-rw-r--r--src/services/index.ts2
-rw-r--r--src/store/actions/index.ts2
-rw-r--r--src/store/actions/userFollow.ts57
-rw-r--r--src/store/actions/userFriends.ts52
-rw-r--r--src/store/actions/userX.ts30
-rw-r--r--src/store/initialStates.ts13
-rw-r--r--src/store/reducers/index.ts2
-rw-r--r--src/store/reducers/userFollowReducer.ts27
-rw-r--r--src/store/reducers/userFriendsReducer.ts25
-rw-r--r--src/store/reducers/userXReducer.ts13
-rw-r--r--src/store/rootReducer.ts4
-rw-r--r--src/types/types.ts6
-rw-r--r--src/utils/common.ts15
-rw-r--r--src/utils/users.ts4
36 files changed, 601 insertions, 472 deletions
diff --git a/src/assets/universities/brown.png b/src/assets/universities/brown.png
new file mode 100644
index 00000000..1f1fd887
--- /dev/null
+++ b/src/assets/universities/brown.png
Binary files differ
diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx
index f533e42d..38e4d597 100644
--- a/src/components/notifications/Notification.tsx
+++ b/src/components/notifications/Notification.tsx
@@ -63,7 +63,7 @@ const Notification: React.FC<NotificationProps> = (props) => {
const onNotificationTap = async () => {
switch (notification_type) {
- case 'FLO':
+ case 'FRD':
if (!userXInStore(state, screenType, id)) {
await fetchUserX(
dispatch,
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 17713ea3..a6c3abdc 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -33,10 +33,10 @@ import ProfileHeader from './ProfileHeader';
import {useDispatch, useSelector, useStore} from 'react-redux';
import {RootState} from '../../store/rootreducer';
import {
- followUnfollowUser,
+ friendUnfriendUser,
blockUnblockUser,
- loadFollowData,
- updateUserXFollowersAndFollowing,
+ loadFriendsData,
+ updateUserXFriends,
updateMomentCategories,
} from '../../store/actions';
import {
@@ -49,7 +49,6 @@ import {
import {Cover} from '.';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {useNavigation} from '@react-navigation/native';
-import {deleteMomentCategories} from '../../services';
interface ContentProps {
y: Animated.Value<number>;
@@ -64,9 +63,13 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.user);
- const {followers = EMPTY_PROFILE_PREVIEW_LIST} = userXId
+ const {friends = EMPTY_PROFILE_PREVIEW_LIST} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.follow);
+ : useSelector((state: RootState) => state.friends);
+
+ const {
+ friends: friendsLoggedInUser = EMPTY_PROFILE_PREVIEW_LIST,
+ } = useSelector((state: RootState) => state.friends);
const {moments = EMPTY_MOMENTS_LIST} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
@@ -92,7 +95,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
const [imagesMap, setImagesMap] = useState<Map<string, MomentType[]>>(
new Map(),
);
- const [isFollowed, setIsFollowed] = useState<boolean>(false);
+ const [isFriend, setIsFriend] = useState<boolean>(false);
const [isBlocked, setIsBlocked] = useState<boolean>(false);
const [profileBodyHeight, setProfileBodyHeight] = useState(0);
const [shouldBounce, setShouldBounce] = useState<boolean>(true);
@@ -143,16 +146,16 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
}, [createImagesMap]);
/**
- * This hook is called on load of profile and when you update the followers list.
+ * This hook is called on load of profile and when you update the friends list.
*/
useEffect(() => {
- const isActuallyFollowed = followers.some(
- (follower) => follower.username === loggedInUser.username,
+ const isActuallyAFriend = friendsLoggedInUser.some(
+ (friend) => friend.username === user.username,
);
- if (isFollowed != isActuallyFollowed) {
- setIsFollowed(isActuallyFollowed);
+ if (isFriend != isActuallyAFriend) {
+ setIsFriend(isActuallyAFriend);
}
- }, [followers]);
+ }, [friends]);
useEffect(() => {
const isActuallyBlocked = blockedUsers.some(
@@ -164,7 +167,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
}, [blockedUsers, user]);
/**
- * The object returned by this method is added to the list of blocked / followed users by the reducer.
+ * The object returned by this method is added to the list of blocked / friended users by the reducer.
* Which helps us prevent an extra api call to the backend just to fetch a user.
*/
const getUserAsProfilePreviewType = (
@@ -181,28 +184,28 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
};
/**
- * Handles a click on the follow / unfollow button.
- * followUnfollowUser takes care of updating the following list for loggedInUser
- * updateUserXFollowersAndFollowing updates followers and following list for the followed user.
+ * Handles a click on the friend / unfriend button.
+ * friendUnfriendUser takes care of updating the friends list for loggedInUser
+ * updateUserXFriends updates friends list for the new friend.
*/
- const handleFollowUnfollow = async () => {
+ const handleFriendUnfriend = async () => {
await dispatch(
- followUnfollowUser(
+ friendUnfriendUser(
loggedInUser,
getUserAsProfilePreviewType(user, profile),
- isFollowed,
+ isFriend,
),
);
- await dispatch(updateUserXFollowersAndFollowing(user.userId, state));
+ await dispatch(updateUserXFriends(user.userId, state));
};
/**
* Handles a click on the block / unblock button.
- * loadFollowData updates followers / following list for the logged in user
- * updateUserXFollowersAndFollowing updates followers and following list for the followed user.
+ * loadFriends updates friends list for the logged in user
+ * updateUserXFriends updates friends list for the user.
*/
- const handleBlockUnblock = async () => {
+ const handleBlockUnblock = async (callback?: () => void) => {
await dispatch(
blockUnblockUser(
loggedInUser,
@@ -210,8 +213,11 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
isBlocked,
),
);
- await dispatch(loadFollowData(loggedInUser.userId));
- await dispatch(updateUserXFollowersAndFollowing(user.userId, state));
+ await dispatch(loadFriendsData(loggedInUser.userId));
+ await dispatch(updateUserXFriends(user.userId, state));
+ if (callback) {
+ callback();
+ }
};
/**
@@ -272,16 +278,18 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
}>
<Cover {...{userXId, screenType}} />
<ProfileCutout />
- <ProfileHeader {...{userXId, screenType}} />
+ <ProfileHeader
+ {...{userXId, screenType, handleBlockUnblock, isBlocked}}
+ />
<ProfileBody
{...{
onLayout,
userXId,
screenType,
- isFollowed,
- handleFollowUnfollow,
- isBlocked,
+ isFriend,
+ handleFriendUnfriend,
handleBlockUnblock,
+ isBlocked,
}}
/>
<TaggsBar {...{y, profileBodyHeight, userXId, screenType}} />
diff --git a/src/components/profile/Followers.tsx b/src/components/profile/Friends.tsx
index 44ae4399..23ce28fe 100644
--- a/src/components/profile/Followers.tsx
+++ b/src/components/profile/Friends.tsx
@@ -1,21 +1,16 @@
import React from 'react';
-import {View, StyleSheet, ViewProps, Text} from 'react-native';
+import {View, StyleSheet, Text} from 'react-native';
import {ProfilePreviewType, ScreenType} from '../../types';
import {ProfilePreview} from '..';
import {useNavigation} from '@react-navigation/native';
import {Button} from 'react-native-elements';
-interface FollowersListProps {
+interface FriendsProps {
result: Array<ProfilePreviewType>;
- sectionTitle: string;
screenType: ScreenType;
}
-const Followers: React.FC<FollowersListProps> = ({
- result,
- sectionTitle,
- screenType,
-}) => {
+const Friends: React.FC<FriendsProps> = ({result, screenType}) => {
const navigation = useNavigation();
return (
<>
@@ -28,14 +23,14 @@ const Followers: React.FC<FollowersListProps> = ({
navigation.pop();
}}
/>
- <Text style={styles.title}>{sectionTitle}</Text>
+ <Text style={styles.title}>Friends</Text>
</View>
{result.map((profilePreview) => (
<ProfilePreview
- style={styles.follower}
+ style={styles.friend}
key={profilePreview.id}
{...{profilePreview}}
- previewType={'Follow'}
+ previewType={'Friend'}
screenType={screenType}
/>
))}
@@ -45,7 +40,7 @@ const Followers: React.FC<FollowersListProps> = ({
const styles = StyleSheet.create({
header: {flexDirection: 'row'},
- follower: {
+ friend: {
marginVertical: 10,
},
title: {
@@ -67,4 +62,4 @@ const styles = StyleSheet.create({
},
});
-export default Followers;
+export default Friends;
diff --git a/src/components/profile/FollowCount.tsx b/src/components/profile/FriendsCount.tsx
index 95b953dc..91d54cab 100644
--- a/src/components/profile/FollowCount.tsx
+++ b/src/components/profile/FriendsCount.tsx
@@ -5,32 +5,23 @@ import {useNavigation} from '@react-navigation/native';
import {RootState} from '../../store/rootReducer';
import {useSelector} from 'react-redux';
import {ScreenType} from '../../types';
-import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates';
-interface FollowCountProps extends ViewProps {
- mode: 'followers' | 'following';
+interface FriendsCountProps extends ViewProps {
userXId: string | undefined;
screenType: ScreenType;
}
-const FollowCount: React.FC<FollowCountProps> = ({
+const FriendsCount: React.FC<FriendsCountProps> = ({
style,
- mode,
userXId,
screenType,
}) => {
- const {
- followers = EMPTY_PROFILE_PREVIEW_LIST,
- following = EMPTY_PROFILE_PREVIEW_LIST,
- } = userXId
+ const count = (userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.follow);
+ : useSelector((state: RootState) => state.friends)
+ )?.friends.length;
- const isFollowers = mode === 'followers';
- const count = isFollowers ? followers.length : following.length;
-
- const navigation = useNavigation();
- const displayed: string =
+ const displayedCount: string =
count < 5e3
? `${count}`
: count < 1e5
@@ -38,20 +29,21 @@ const FollowCount: React.FC<FollowCountProps> = ({
: count < 1e6
? `${(count / 1e3).toFixed(0)}k`
: `${count / 1e6}m`;
+
+ const navigation = useNavigation();
+
return (
<TouchableOpacity
+ style={{right: '20%'}}
onPress={() =>
- navigation.push('FollowersListScreen', {
- isFollowers,
+ navigation.push('FriendsListScreen', {
userXId,
screenType,
})
}>
<View style={[styles.container, style]}>
- <Text style={styles.count}>{displayed}</Text>
- <Text style={styles.label}>
- {isFollowers ? 'Followers' : 'Following'}
- </Text>
+ <Text style={styles.count}>{displayedCount}</Text>
+ <Text style={styles.label}>Friends</Text>
</View>
</TouchableOpacity>
);
@@ -66,9 +58,9 @@ const styles = StyleSheet.create({
fontSize: 18,
},
label: {
- fontWeight: '400',
+ fontWeight: '500',
fontSize: 16,
},
});
-export default FollowCount;
+export default FriendsCount;
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index 85634daa..70f98a4b 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -9,18 +9,18 @@ import {NO_PROFILE} from '../../store/initialStates';
interface ProfileBodyProps {
onLayout: (event: LayoutChangeEvent) => void;
- isFollowed: boolean;
+ isFriend: boolean;
isBlocked: boolean;
- handleFollowUnfollow: Function;
- handleBlockUnblock: Function;
+ handleFriendUnfriend: () => void;
+ handleBlockUnblock: () => void;
userXId: string | undefined;
screenType: ScreenType;
}
const ProfileBody: React.FC<ProfileBodyProps> = ({
onLayout,
- isFollowed,
+ isFriend,
isBlocked,
- handleFollowUnfollow,
+ handleFriendUnfriend,
handleBlockUnblock,
userXId,
screenType,
@@ -48,21 +48,26 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
);
}}>{`${website}`}</Text>
)}
- {userXId && (
+
+ {userXId && isBlocked && (
<View style={styles.toggleButtonContainer}>
- {!isBlocked && (
- <ToggleButton
- toggleState={isFollowed}
- handleToggle={handleFollowUnfollow}
- buttonType={TOGGLE_BUTTON_TYPE.FOLLOW_UNFOLLOW}
- />
- )}
<ToggleButton
toggleState={isBlocked}
handleToggle={handleBlockUnblock}
buttonType={TOGGLE_BUTTON_TYPE.BLOCK_UNBLOCK}
/>
</View>
+
+ )}
+ {userXId && !isBlocked && (
+ <View style={styles.toggleButtonContainer}>
+ <ToggleButton
+ toggleState={isFriend}
+ handleToggle={handleFriendUnfriend}
+ buttonType={TOGGLE_BUTTON_TYPE.FRIEND_UNFRIEND}
+ />
+ </View>
+
)}
</View>
);
diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx
index 0eb2d469..5c14b374 100644
--- a/src/components/profile/ProfileHeader.tsx
+++ b/src/components/profile/ProfileHeader.tsx
@@ -1,20 +1,31 @@
import React, {useState} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {useSelector} from 'react-redux';
+import {UniversityIcon} from '.';
import {RootState} from '../../store/rootreducer';
import {ScreenType} from '../../types';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
import Avatar from './Avatar';
-import FollowCount from './FollowCount';
+import FriendsCount from './FriendsCount';
import ProfileMoreInfoDrawer from './ProfileMoreInfoDrawer';
type ProfileHeaderProps = {
userXId: string | undefined;
screenType: ScreenType;
+ isBlocked: boolean;
+ handleBlockUnblock: () => void;
};
-const ProfileHeader: React.FC<ProfileHeaderProps> = ({userXId, screenType}) => {
- const {profile: {name = ''} = {}} = userXId
+const ProfileHeader: React.FC<ProfileHeaderProps> = ({
+ userXId,
+ screenType,
+ isBlocked,
+ handleBlockUnblock,
+}) => {
+ const {
+ profile: {name = '', university_class = 2021} = {},
+ user: {username: userXName = ''},
+ } = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.user);
@@ -22,12 +33,15 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({userXId, screenType}) => {
return (
<View style={styles.container}>
- {!userXId && (
- <ProfileMoreInfoDrawer
- isOpen={drawerVisible}
- setIsOpen={setDrawerVisible}
- />
- )}
+ <ProfileMoreInfoDrawer
+ isOpen={drawerVisible}
+ isBlocked={isBlocked}
+ handleBlockUnblock={handleBlockUnblock}
+ userXId={userXId}
+ userXName={userXName}
+ setIsOpen={setDrawerVisible}
+ />
+
<View style={styles.row}>
<Avatar
style={styles.avatar}
@@ -36,18 +50,17 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({userXId, screenType}) => {
/>
<View style={styles.header}>
<Text style={styles.name}>{name}</Text>
- <View style={styles.row}>
- <FollowCount
- style={styles.follows}
- mode="followers"
+
+ <View style={styles.friendsAndUniversity}>
+ <FriendsCount
+ style={styles.friends}
screenType={screenType}
userXId={userXId}
/>
- <FollowCount
- style={styles.follows}
- mode="following"
- screenType={screenType}
- userXId={userXId}
+ <UniversityIcon
+ style={styles.university}
+ university="brown"
+ university_class={university_class}
/>
</View>
</View>
@@ -66,8 +79,8 @@ const styles = StyleSheet.create({
flexDirection: 'row',
},
header: {
+ flexDirection: 'column',
justifyContent: 'center',
- alignItems: 'center',
marginTop: SCREEN_HEIGHT / 40,
marginLeft: SCREEN_WIDTH / 10,
marginBottom: SCREEN_HEIGHT / 50,
@@ -77,13 +90,19 @@ const styles = StyleSheet.create({
left: '10%',
},
name: {
+ marginHorizontal: '20%',
fontSize: 20,
- fontWeight: '700',
+ fontWeight: '500',
+ alignSelf: 'center',
marginBottom: SCREEN_HEIGHT / 80,
},
- follows: {
- marginHorizontal: SCREEN_HEIGHT / 50,
+ friends: {
+ marginRight: SCREEN_HEIGHT / 80,
+ },
+ university: {
+ marginLeft: SCREEN_HEIGHT / 50,
},
+ friendsAndUniversity: {flexDirection: 'row', flex: 1, marginLeft: '20%'},
});
export default ProfileHeader;
diff --git a/src/components/profile/ProfileMoreInfoDrawer.tsx b/src/components/profile/ProfileMoreInfoDrawer.tsx
index 80ad9bba..76f0f27f 100644
--- a/src/components/profile/ProfileMoreInfoDrawer.tsx
+++ b/src/components/profile/ProfileMoreInfoDrawer.tsx
@@ -12,14 +12,19 @@ import {GenericMoreInfoDrawer} from '../common';
interface ProfileMoreInfoDrawerProps {
isOpen: boolean;
setIsOpen: (visible: boolean) => void;
+ userXId: string | undefined;
+ userXName: string;
+ isBlocked: boolean;
+ handleBlockUnblock: (callback?: () => void) => void;
}
const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
- const {setIsOpen} = props;
const navigation = useNavigation();
+ const {setIsOpen, userXId, isBlocked, handleBlockUnblock, userXName} = props;
const {
user: {userId, username},
} = useSelector((state: RootState) => state.user);
+ const isOwnProfile = !userXId || userXName === username;
const goToEditProfile = () => {
navigation.push('EditProfile', {
@@ -29,6 +34,10 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
setIsOpen(false);
};
+ const onBlockUnblock = () => {
+ handleBlockUnblock(() => setIsOpen(false));
+ };
+
return (
<>
<TouchableOpacity
@@ -38,14 +47,29 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
}}>
<MoreIcon height={30} width={30} color={TAGG_DARK_BLUE} />
</TouchableOpacity>
- <GenericMoreInfoDrawer
- {...props}
- showIcons={true}
- textColor={'black'}
- buttons={[
- ['Edit Profile', goToEditProfile, <PersonOutline color="black" />],
- ]}
- />
+ {!isOwnProfile ? (
+ <GenericMoreInfoDrawer
+ {...props}
+ showIcons={false}
+ textColor={'red'}
+ buttons={[
+ [
+ (isBlocked ? 'Unblock' : 'Block') + ` ${userXName}`,
+ onBlockUnblock,
+ undefined,
+ ],
+ ]}
+ />
+ ) : (
+ <GenericMoreInfoDrawer
+ {...props}
+ showIcons={true}
+ textColor={'black'}
+ buttons={[
+ ['Edit Profile', goToEditProfile, <PersonOutline color="black" />],
+ ]}
+ />
+ )}
</>
);
};
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
index 49c79e2d..bd015811 100644
--- a/src/components/profile/ProfilePreview.tsx
+++ b/src/components/profile/ProfilePreview.tsx
@@ -28,7 +28,7 @@ const NO_USER: UserType = {
};
/**
- * This component returns user's profile picture followed by username as a touchable component.
+ * This component returns user's profile picture friended by username as a touchable component.
* What happens when someone clicks on this component is partly decided by the prop isComment.
* If isComment is true then it means that we are not displaying this tile as a part of search results.
* And hence we do not cache the search results.
@@ -189,13 +189,13 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
usernameStyle = styles.commentUsername;
nameStyle = styles.commentName;
break;
- case 'Follow':
- containerStyle = styles.followContainer;
- avatarStyle = styles.followAvatar;
- nameContainerStyle = styles.followNameContainer;
+ case 'Friend':
+ containerStyle = styles.friendContainer;
+ avatarStyle = styles.friendAvatar;
+ nameContainerStyle = styles.friendNameContainer;
usernameToDisplay = '@' + username;
- usernameStyle = styles.followUsername;
- nameStyle = styles.followName;
+ usernameStyle = styles.friendUsername;
+ nameStyle = styles.friendName;
break;
default:
containerStyle = styles.searchResultContainer;
@@ -233,7 +233,7 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
<Text style={usernameStyle}>{usernameToDisplay}</Text>
</>
)}
- {previewType === 'Follow' && (
+ {previewType === 'Friend' && (
<>
<Text style={usernameStyle}>{usernameToDisplay}</Text>
<Text style={nameStyle}>{first_name.concat(' ', last_name)}</Text>
@@ -319,28 +319,28 @@ const styles = StyleSheet.create({
color: 'white',
textAlign: 'center',
},
- followContainer: {
+ friendContainer: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 10,
},
- followAvatar: {
+ friendAvatar: {
height: 42,
width: 42,
marginRight: 15,
borderRadius: 20,
},
- followNameContainer: {
+ friendNameContainer: {
justifyContent: 'space-evenly',
alignSelf: 'stretch',
},
- followUsername: {
+ friendUsername: {
fontSize: 14,
fontWeight: '700',
color: '#3C3C3C',
letterSpacing: 0.6,
},
- followName: {
+ friendName: {
fontSize: 12,
fontWeight: '500',
color: '#6C6C6C',
diff --git a/src/components/profile/ToggleButton.tsx b/src/components/profile/ToggleButton.tsx
index 88eb9f8a..df7380d7 100644
--- a/src/components/profile/ToggleButton.tsx
+++ b/src/components/profile/ToggleButton.tsx
@@ -1,8 +1,8 @@
import * as React from 'react';
import {StyleSheet, Text} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
-import { TAGG_TEXT_LIGHT_BLUE } from '../../constants';
-import {getToggleButtonText, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {getToggleButtonText, SCREEN_WIDTH} from '../../utils';
type ToggleButtonProps = {
toggleState: boolean;
@@ -15,9 +15,15 @@ const ToggleButton: React.FC<ToggleButtonProps> = ({
handleToggle,
buttonType,
}) => {
+ const buttonColor = toggleState
+ ? styles.buttonColorToggled
+ : styles.buttonColor;
+ const textColor = toggleState ? styles.textColorToggled : styles.textColor;
return (
- <TouchableOpacity style={styles.button} onPress={() => handleToggle()}>
- <Text style={styles.text}>
+ <TouchableOpacity
+ style={[styles.button, buttonColor]}
+ onPress={() => handleToggle()}>
+ <Text style={[styles.text, textColor]}>
{getToggleButtonText(buttonType, toggleState)}
</Text>
</TouchableOpacity>
@@ -30,15 +36,18 @@ const styles = StyleSheet.create({
alignItems: 'center',
width: SCREEN_WIDTH * 0.25,
height: SCREEN_WIDTH * 0.1,
- borderRadius: 8,
borderColor: TAGG_TEXT_LIGHT_BLUE,
- backgroundColor: 'white',
borderWidth: 3,
marginRight: '2%',
},
text: {
fontWeight: 'bold',
- color: TAGG_TEXT_LIGHT_BLUE,
},
+ buttonColor: {
+ backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ },
+ textColor: {color: 'white'},
+ buttonColorToggled: {backgroundColor: 'white'},
+ textColorToggled: {color: TAGG_TEXT_LIGHT_BLUE},
});
export default ToggleButton;
diff --git a/src/components/profile/UniversityIcon.tsx b/src/components/profile/UniversityIcon.tsx
new file mode 100644
index 00000000..15c23715
--- /dev/null
+++ b/src/components/profile/UniversityIcon.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import {StyleSheet, ViewProps} from 'react-native';
+import {Image, Text, View} from 'react-native-animatable';
+import {getUniversityClass} from '../../utils';
+
+export interface UniversityIconProps extends ViewProps {
+ university: string;
+ university_class: number;
+}
+
+/**
+ * Component to display university icon and class
+ */
+const UniversityIcon: React.FC<UniversityIconProps> = ({
+ style,
+ university,
+ university_class,
+}) => {
+ var universityIcon;
+ switch (university) {
+ case 'brown':
+ universityIcon = require('../../assets/universities/brown.png');
+ break;
+ default:
+ universityIcon = require('../../assets/universities/brown.png');
+ break;
+ }
+
+ return (
+ <View style={[styles.container, style]}>
+ <Image source={universityIcon} style={styles.icon} />
+ <Text style={styles.univClass}>
+ {getUniversityClass(university_class)}
+ </Text>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ marginBottom: '10%',
+ },
+ univClass: {
+ fontSize: 15,
+ fontWeight: '500',
+ },
+ icon: {
+ alignSelf: 'center',
+ width: 20,
+ height: 22.5,
+ },
+});
+
+export default UniversityIcon;
diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts
index dc3872b1..260f4217 100644
--- a/src/components/profile/index.ts
+++ b/src/components/profile/index.ts
@@ -4,6 +4,7 @@ export {default as ProfileCutout} from './ProfileCutout';
export {default as ProfileBody} from './ProfileBody';
export {default as ProfileHeader} from './ProfileHeader';
export {default as ProfilePreview} from './ProfilePreview';
-export {default as Followers} from './Followers';
+export {default as Friends} from './Friends';
export {default as ProfileMoreInfoDrawer} from './ProfileMoreInfoDrawer';
export {default as MomentMoreInfoDrawer} from './MomentMoreInfoDrawer';
+export {default as UniversityIcon} from './UniversityIcon';
diff --git a/src/constants/api.ts b/src/constants/api.ts
index 2118492d..3b2289fd 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -20,10 +20,7 @@ export const SEARCH_ENDPOINT: string = API_URL + 'search/';
export const MOMENTS_ENDPOINT: string = API_URL + 'moments/';
export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/';
export const COMMENTS_ENDPOINT: string = API_URL + 'comments/';
-export const FOLLOW_USER_ENDPOINT: string = API_URL + 'follow/';
-export const UNFOLLOW_USER_ENDPOINT: string = API_URL + 'unfollow/';
-export const FOLLOWERS_ENDPOINT: string = API_URL + 'followers/';
-export const FOLLOWING_ENDPOINT: string = API_URL + 'following/';
+export const FRIENDS_ENDPOINT: string = API_URL + 'friends/';
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/';
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index 52a52de6..a85af7cd 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -87,7 +87,7 @@ export const SOCIAL_FONT_COLORS = {
};
export const TOGGLE_BUTTON_TYPE = {
- FOLLOW_UNFOLLOW: 'Follow',
+ FRIEND_UNFRIEND: 'Friend',
BLOCK_UNBLOCK: 'Block',
};
@@ -130,3 +130,15 @@ export const BACKGROUND_GRADIENT_MAP: Record<
[BackgroundGradientType.Light]: ['#9F00FF', '#27EAE9'],
[BackgroundGradientType.Dark]: ['#421566', '#385D5E'],
};
+
+export const CLASS_YEAR_LIST: Array<string> = [
+ '2018',
+ '2019',
+ '2020',
+ '2021',
+ '2022',
+ '2023',
+ '2024',
+ '2025',
+ '2026',
+];
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index c156c725..4614168b 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -32,8 +32,7 @@ export type MainStackParams = {
userXId: string | undefined;
screenType: ScreenType;
};
- FollowersListScreen: {
- isFollowers: boolean;
+ FriendsListScreen: {
userXId: string | undefined;
screenType: ScreenType;
};
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index cd053bde..bf643fd8 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -6,9 +6,9 @@ import {
SearchScreen,
ProfileScreen,
MomentCommentsScreen,
- FollowersListScreen,
EditProfile,
CategorySelection,
+ FriendsListScreen,
NotificationsScreen,
} from '../../screens';
import {MainStack, MainStackParams} from './MainStackNavigator';
@@ -157,8 +157,8 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
initialParams={{screenType}}
/>
<MainStack.Screen
- name="FollowersListScreen"
- component={FollowersListScreen}
+ name="FriendsListScreen"
+ component={FriendsListScreen}
options={{
...modalStyle,
}}
diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx
index 611f1598..d51aec95 100644
--- a/src/screens/onboarding/ProfileOnboarding.tsx
+++ b/src/screens/onboarding/ProfileOnboarding.tsx
@@ -25,9 +25,13 @@ import {
websiteRegex,
bioRegex,
genderRegex,
+ CLASS_YEAR_LIST,
} from '../../constants';
import AsyncStorage from '@react-native-community/async-storage';
import {BackgroundGradientType} from '../../types';
+import {PickerSelectProps} from 'react-native-picker-select';
+import Animated from 'react-native-reanimated';
+import {SCREEN_WIDTH} from '../../utils';
type ProfileOnboardingScreenRouteProp = RouteProp<
OnboardingStackParams,
@@ -65,6 +69,7 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
isValidGender: true,
attemptedSubmit: false,
token: '',
+ classYear: -1,
});
const [customGender, setCustomGender] = React.useState(Boolean);
@@ -100,6 +105,12 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
}
};
+ var classYearList: Array<any> = [];
+
+ CLASS_YEAR_LIST.map((value) => {
+ classYearList.push({label: value, value: value});
+ });
+
/**
* Profile screen "Add header image" button
*/
@@ -212,6 +223,14 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
}
};
+ const handleClassYearUpdate = (value: string) => {
+ const classYear = Number.parseInt(value);
+ setForm({
+ ...form,
+ classYear,
+ });
+ };
+
const handleCustomGenderUpdate = (gender: string) => {
let isValidGender: boolean = genderRegex.test(gender);
gender = gender.replace(' ', '-');
@@ -238,6 +257,10 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
Alert.alert('Please select a Profile Picture!');
return;
}
+ if (form.classYear === -1) {
+ Alert.alert('Please select Class Year');
+ return;
+ }
if (!form.attemptedSubmit) {
setForm({
...form,
@@ -298,6 +321,10 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
}
}
+ if (form.classYear !== -1) {
+ request.append('university_class', form.classYear);
+ }
+
if (invalidFields) {
return;
}
@@ -344,96 +371,118 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({
};
return (
- <Background centered gradientType={BackgroundGradientType.Light}>
- <StatusBar barStyle="light-content" />
- <View style={styles.profile}>
- <LargeProfilePic />
- <SmallProfilePic />
- </View>
- <View style={styles.contentContainer}>
- <TaggInput
- accessibilityHint="Enter a website."
- accessibilityLabel="Website input field."
- placeholder="Website"
- autoCompleteType="off"
- textContentType="URL"
- autoCapitalize="none"
- returnKeyType="next"
- onChangeText={handleWebsiteUpdate}
- onSubmitEditing={() => handleFocusChange('bio')}
- blurOnSubmit={false}
- valid={form.isValidWebsite}
- attemptedSubmit={form.attemptedSubmit}
- invalidWarning={'Website must be a valid link to your website'}
- width={280}
- />
- <TaggBigInput
- accessibilityHint="Enter a bio."
- accessibilityLabel="Bio input field."
- placeholder="Bio"
- autoCompleteType="off"
- textContentType="none"
- autoCapitalize="none"
- returnKeyType="next"
- onChangeText={handleBioUpdate}
- onSubmitEditing={() => handleFocusChange('bio')}
- blurOnSubmit={false}
- ref={bioRef}
- valid={form.isValidBio}
- attemptedSubmit={form.attemptedSubmit}
- invalidWarning={
- 'Bio must be less than 150 characters and must contain valid characters'
- }
- width={280}
- />
- <BirthDatePicker
- ref={birthdateRef}
- handleBDUpdate={handleBirthdateUpdate}
- width={280}
- date={form.birthdate}
- showPresetdate={false}
- />
- <TaggDropDown
- onValueChange={(value: string) => handleGenderUpdate(value)}
- items={[
- {label: 'Male', value: 'male'},
- {label: 'Female', value: 'female'},
- {label: 'Custom', value: 'custom'},
- ]}
- placeholder={{
- label: 'Gender',
- value: null,
- color: '#ddd',
- }}
- />
- {customGender && (
+ <Animated.ScrollView bounces={false}>
+ <Background
+ centered
+ gradientType={BackgroundGradientType.Light}
+ style={styles.container}>
+ <StatusBar barStyle="light-content" />
+ <View style={styles.profile}>
+ <LargeProfilePic />
+ <SmallProfilePic />
+ </View>
+ <View style={styles.contentContainer}>
<TaggInput
- accessibilityHint="Custom"
- accessibilityLabel="Gender input field."
- placeholder="Enter your gender"
+ accessibilityHint="Enter a website."
+ accessibilityLabel="Website input field."
+ placeholder="Website"
+ autoCompleteType="off"
+ textContentType="URL"
+ autoCapitalize="none"
+ returnKeyType="next"
+ onChangeText={handleWebsiteUpdate}
+ onSubmitEditing={() => handleFocusChange('bio')}
+ blurOnSubmit={false}
+ valid={form.isValidWebsite}
+ attemptedSubmit={form.attemptedSubmit}
+ invalidWarning={'Website must be a valid link to your website'}
+ width={280}
+ />
+ <TaggBigInput
+ accessibilityHint="Enter a bio."
+ accessibilityLabel="Bio input field."
+ placeholder="Bio"
autoCompleteType="off"
textContentType="none"
autoCapitalize="none"
returnKeyType="next"
+ onChangeText={handleBioUpdate}
+ onSubmitEditing={() => handleFocusChange('bio')}
blurOnSubmit={false}
- ref={customGenderRef}
- onChangeText={handleCustomGenderUpdate}
- onSubmitEditing={() => handleSubmit()}
- valid={form.isValidGender}
+ ref={bioRef}
+ valid={form.isValidBio}
attemptedSubmit={form.attemptedSubmit}
- invalidWarning={'Custom field can only contain letters and hyphens'}
+ invalidWarning={
+ 'Bio must be less than 150 characters and must contain valid characters'
+ }
width={280}
/>
- )}
- <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn}>
- <Text style={styles.submitBtnLabel}>Let's start!</Text>
- </TouchableOpacity>
- </View>
- </Background>
+ <BirthDatePicker
+ ref={birthdateRef}
+ handleBDUpdate={handleBirthdateUpdate}
+ width={280}
+ date={form.birthdate}
+ showPresetdate={false}
+ />
+ <TaggDropDown
+ onValueChange={(value: string) => handleClassYearUpdate(value)}
+ items={classYearList}
+ placeholder={{
+ label: 'Class Year',
+ value: null,
+ color: '#ddd',
+ }}
+ />
+ {customGender && (
+ <TaggInput
+ accessibilityHint="Custom"
+ accessibilityLabel="Gender input field."
+ placeholder="Enter your gender"
+ autoCompleteType="off"
+ textContentType="none"
+ autoCapitalize="none"
+ returnKeyType="next"
+ blurOnSubmit={false}
+ ref={customGenderRef}
+ onChangeText={handleCustomGenderUpdate}
+ onSubmitEditing={() => handleSubmit()}
+ valid={form.isValidGender}
+ attemptedSubmit={form.attemptedSubmit}
+ invalidWarning={
+ 'Custom field can only contain letters and hyphens'
+ }
+ width={280}
+ />
+ )}
+ <TaggDropDown
+ onValueChange={(value: string) => handleGenderUpdate(value)}
+ items={[
+ {label: 'Male', value: 'male'},
+ {label: 'Female', value: 'female'},
+ {label: 'Custom', value: 'custom'},
+ ]}
+ placeholder={{
+ label: 'Gender',
+ value: null,
+ color: '#ddd',
+ }}
+ />
+ <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn}>
+ <Text style={styles.submitBtnLabel}>Let's start!</Text>
+ </TouchableOpacity>
+ </View>
+ </Background>
+ </Animated.ScrollView>
);
};
const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ marginBottom: '10%',
+ },
profile: {
flexDirection: 'row',
marginBottom: '5%',
@@ -449,7 +498,7 @@ const styles = StyleSheet.create({
padding: 15,
backgroundColor: '#fff',
marginLeft: '13%',
- marginTop: '5%',
+ marginTop: '10%',
height: 230,
width: 230,
borderRadius: 23,
@@ -491,11 +540,12 @@ const styles = StyleSheet.create({
backgroundColor: '#8F01FF',
justifyContent: 'center',
alignItems: 'center',
- width: 150,
- height: 40,
+ width: SCREEN_WIDTH / 2.5,
+ height: SCREEN_WIDTH / 10,
borderRadius: 5,
marginTop: '5%',
alignSelf: 'center',
+ marginBottom: SCREEN_WIDTH / 3,
},
submitBtnLabel: {
fontSize: 16,
diff --git a/src/screens/profile/FollowersListScreen.tsx b/src/screens/profile/FriendsListScreen.tsx
index 874dd01b..f7192d10 100644
--- a/src/screens/profile/FollowersListScreen.tsx
+++ b/src/screens/profile/FriendsListScreen.tsx
@@ -1,6 +1,6 @@
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
import {RouteProp} from '@react-navigation/native';
-import {TabsGradient, Followers, CenteredView} from '../../components';
+import {TabsGradient, Friends, CenteredView} from '../../components';
import {ScrollView} from 'react-native-gesture-handler';
import {SCREEN_HEIGHT} from '../../utils';
import {StyleSheet, View} from 'react-native';
@@ -10,28 +10,20 @@ import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates';
import {useSelector} from 'react-redux';
import {RootState} from '../../store/rootReducer';
-type FollowersListScreenRouteProp = RouteProp<
+type FriendsListScreenRouteProp = RouteProp<
ProfileStackParams,
- 'FollowersListScreen'
+ 'FriendsListScreen'
>;
-interface FollowersListScreenProps {
- route: FollowersListScreenRouteProp;
+interface FriendsListScreenProps {
+ route: FriendsListScreenRouteProp;
}
-const FollowersListScreen: React.FC<FollowersListScreenProps> = ({route}) => {
- const {isFollowers, userXId, screenType} = route.params;
+const FriendsListScreen: React.FC<FriendsListScreenProps> = ({route}) => {
+ const {userXId, screenType} = route.params;
- const {followers, following} = userXId
+ const {friends} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.follow);
-
- const [list, setList] = useState<ProfilePreviewType[]>(
- EMPTY_PROFILE_PREVIEW_LIST,
- );
-
- useEffect(() => {
- setList(isFollowers ? followers : following);
- }, [followers, following, setList]);
+ : useSelector((state: RootState) => state.friends);
return (
<CenteredView>
@@ -41,11 +33,7 @@ const FollowersListScreen: React.FC<FollowersListScreenProps> = ({route}) => {
stickyHeaderIndices={[4]}
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false}>
- <Followers
- result={list}
- sectionTitle={isFollowers ? 'Followers' : 'Following'}
- screenType={screenType}
- />
+ <Friends result={friends} screenType={screenType} />
</ScrollView>
<TabsGradient />
</View>
@@ -80,4 +68,4 @@ const styles = StyleSheet.create({
},
});
-export default FollowersListScreen;
+export default FriendsListScreen;
diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts
index 3bfe5d30..b6a13144 100644
--- a/src/screens/profile/index.ts
+++ b/src/screens/profile/index.ts
@@ -3,5 +3,5 @@ export {default as SocialMediaTaggs} from './SocialMediaTaggs';
export {default as CaptionScreen} from './CaptionScreen';
export {default as IndividualMoment} from './IndividualMoment';
export {default as MomentCommentsScreen} from './MomentCommentsScreen';
-export {default as FollowersListScreen} from './FollowersListScreen';
+export {default as FriendsListScreen} from './FriendsListScreen';
export {default as EditProfile} from './EditProfile';
diff --git a/src/services/UserFollowServices.ts b/src/services/UserFollowServices.ts
deleted file mode 100644
index f0f176fc..00000000
--- a/src/services/UserFollowServices.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-//Abstracted common user follow api calls out here
-
-import {Alert} from 'react-native';
-import {
- FOLLOWERS_ENDPOINT,
- FOLLOW_USER_ENDPOINT,
- UNFOLLOW_USER_ENDPOINT,
- FOLLOWING_ENDPOINT,
-} from '../constants';
-
-export const loadFollowers = async (userId: string, token: string) => {
- try {
- const response = await fetch(FOLLOWERS_ENDPOINT + `?user_id=${userId}`, {
- method: 'GET',
- headers: {
- Authorization: 'Token ' + token,
- },
- });
- if (response.status === 200) {
- const body = await response.json();
- return body;
- } else {
- throw new Error(await response.json());
- }
- } catch (error) {
- console.log(error);
- }
-};
-
-export const loadFollowing = async (userId: string, token: string) => {
- try {
- const response = await fetch(FOLLOWING_ENDPOINT + `?user_id=${userId}`, {
- method: 'GET',
- headers: {
- Authorization: 'Token ' + token,
- },
- });
- if (response.status === 200) {
- const body = await response.json();
- return body;
- } else {
- throw new Error(await response.json());
- }
- } catch (error) {
- console.log(error);
- }
-};
-
-export const followOrUnfollowUser = async (
- follower: string,
- followed: string,
- token: string,
- isFollowed: boolean,
-) => {
- try {
- const endpoint = isFollowed ? UNFOLLOW_USER_ENDPOINT : FOLLOW_USER_ENDPOINT;
- const response = await fetch(endpoint, {
- method: 'POST',
- headers: {
- Authorization: 'Token ' + token,
- },
- body: JSON.stringify({
- follower,
- followed,
- }),
- });
- if (Math.floor(response.status / 100) === 2) {
- return true;
- } else {
- console.log(await response.json());
- Alert.alert(
- 'Something went wrong! 😭',
- "Would you believe me if I told you that I don't know what happened?",
- );
- return false;
- }
- } catch (error) {
- console.log(error);
- Alert.alert(
- 'Something went wrong! 😭',
- "Would you believe me if I told you that I don't know what happened?",
- );
- return false;
- }
-};
diff --git a/src/services/UserFriendsServices.ts b/src/services/UserFriendsServices.ts
new file mode 100644
index 00000000..0b138fc3
--- /dev/null
+++ b/src/services/UserFriendsServices.ts
@@ -0,0 +1,63 @@
+//Abstracted common friends api calls out here
+
+import {Alert} from 'react-native';
+import {FRIENDS_ENDPOINT} from '../constants';
+
+export const loadFriends = async (userId: string, token: string) => {
+ try {
+ const response = await fetch(FRIENDS_ENDPOINT + `?user_id=${userId}`, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ if (response.status === 200) {
+ const body = await response.json();
+ return body;
+ } else {
+ throw new Error(await response.json());
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+export const friendOrUnfriendUser = async (
+ user: string,
+ friend: string,
+ token: string,
+ isFriend: boolean,
+) => {
+ try {
+ const endpoint = FRIENDS_ENDPOINT + (isFriend ? `${user}/` : '');
+ const response = await fetch(endpoint, {
+ method: isFriend ? 'DELETE' : 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Token ' + token,
+ },
+ body: JSON.stringify({
+ user,
+ friend,
+ }),
+ });
+ const status = response.status;
+ if (Math.floor(status / 100) === 2) {
+ return true;
+ } else {
+ console.log(await response.json());
+ Alert.alert(
+ 'Something went wrong! 😭',
+ "Would you believe me if I told you that I don't know what happened?",
+ );
+ return false;
+ }
+ } catch (error) {
+ console.log(error);
+ Alert.alert(
+ 'Something went wrong! 😭',
+ "Would you believe me if I told you that I don't know what happened?",
+ );
+ return false;
+ }
+};
diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts
index 8c88f0ab..75042830 100644
--- a/src/services/UserProfileService.ts
+++ b/src/services/UserProfileService.ts
@@ -29,9 +29,27 @@ export const loadProfileInfo = async (token: string, userId: string) => {
const status = response.status;
if (status === 200) {
const info = await response.json();
- let {name, biography, website, birthday, gender, snapchat, tiktok} = info;
+ let {
+ name,
+ biography,
+ website,
+ birthday,
+ gender,
+ snapchat,
+ tiktok,
+ university_class,
+ } = info;
birthday = birthday && moment(birthday).format('YYYY-MM-DD');
- return {name, biography, website, birthday, gender, snapchat, tiktok};
+ return {
+ name,
+ biography,
+ website,
+ birthday,
+ gender,
+ snapchat,
+ tiktok,
+ university_class,
+ };
} else {
throw 'Unable to load profile data';
}
diff --git a/src/services/index.ts b/src/services/index.ts
index 7e6b836a..7ea5bf5d 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -2,7 +2,7 @@ export * from './UserProfileService';
export * from './SocialLinkingService';
export * from './MomentServices';
export * from './ExploreServices';
-export * from './UserFollowServices';
+export * from './UserFriendsServices';
export * from './ReportingService';
export * from './BlockUserService';
export * from './MomentCategoryService';
diff --git a/src/store/actions/index.ts b/src/store/actions/index.ts
index 285ca4de..337bc325 100644
--- a/src/store/actions/index.ts
+++ b/src/store/actions/index.ts
@@ -1,5 +1,5 @@
export * from './user';
-export * from './userFollow';
+export * from './userFriends';
export * from './userMoments';
export * from './momentCategories';
export * from './socials';
diff --git a/src/store/actions/userFollow.ts b/src/store/actions/userFollow.ts
deleted file mode 100644
index e23bbfc0..00000000
--- a/src/store/actions/userFollow.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import {getTokenOrLogout} from './../../utils';
-import {RootState} from '../rootReducer';
-import {ProfilePreviewType, UserType} from '../../types/types';
-import {
- followOrUnfollowUser,
- loadFollowers,
- loadFollowing,
-} from '../../services';
-import {Action, ThunkAction} from '@reduxjs/toolkit';
-import {userFollowFetched, updateFollowing, userLoggedIn} from '../reducers';
-
-export const loadFollowData = (
- userId: string,
-): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
- dispatch,
-) => {
- try {
- const token = await getTokenOrLogout(dispatch);
- const followers = await loadFollowers(userId, token);
- const following = await loadFollowing(userId, token);
- dispatch({
- type: userFollowFetched.type,
- payload: {followers, following},
- });
- } catch (error) {
- console.log(error);
- }
-};
-
-export const followUnfollowUser = (
- follower: UserType,
- followed: ProfilePreviewType,
- isFollowed: boolean,
-): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
- dispatch,
-) => {
- try {
- const token = await getTokenOrLogout(dispatch);
- const success = await followOrUnfollowUser(
- follower.userId,
- followed.id,
- token,
- isFollowed,
- );
- if (success) {
- dispatch({
- type: updateFollowing.type,
- payload: {
- isFollowed,
- data: followed,
- },
- });
- }
- } catch (error) {
- console.log(error);
- }
-};
diff --git a/src/store/actions/userFriends.ts b/src/store/actions/userFriends.ts
new file mode 100644
index 00000000..24e32607
--- /dev/null
+++ b/src/store/actions/userFriends.ts
@@ -0,0 +1,52 @@
+import {getTokenOrLogout} from '../../utils';
+import {RootState} from '../rootReducer';
+import {ProfilePreviewType, UserType} from '../../types/types';
+import {friendOrUnfriendUser, loadFriends} from '../../services';
+import {Action, ThunkAction} from '@reduxjs/toolkit';
+import {userFriendsFetched, updateFriends} from '../reducers';
+
+export const loadFriendsData = (
+ userId: string,
+): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
+ dispatch,
+) => {
+ try {
+ const token = await getTokenOrLogout(dispatch);
+ const friends = await loadFriends(userId, token);
+ dispatch({
+ type: userFriendsFetched.type,
+ payload: {friends},
+ });
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+export const friendUnfriendUser = (
+ user: UserType,
+ friend: ProfilePreviewType,
+ isFriend: boolean,
+): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
+ dispatch,
+) => {
+ try {
+ const token = await getTokenOrLogout(dispatch);
+ const success = await friendOrUnfriendUser(
+ user.userId,
+ friend.id,
+ token,
+ isFriend,
+ );
+ if (success) {
+ dispatch({
+ type: updateFriends.type,
+ payload: {
+ isFriend,
+ data: friend,
+ },
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
diff --git a/src/store/actions/userX.ts b/src/store/actions/userX.ts
index e313546e..0f87012d 100644
--- a/src/store/actions/userX.ts
+++ b/src/store/actions/userX.ts
@@ -1,4 +1,3 @@
-import {loadMomentCategories} from './../../services/MomentCategoryService';
import {userXInStore} from './../../utils/';
import {getTokenOrLogout, loadAllSocialsForUser} from './../../utils';
import {UserType, ScreenType} from '../../types/types';
@@ -7,8 +6,7 @@ import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
userXRequested,
userXAvatarFetched,
- userXFollowersFetched,
- userXFollowingFetched,
+ userXFriendsFetched,
userXCoverFetched,
userXMomentsFetched,
userXProfileFetched,
@@ -21,8 +19,8 @@ import {
loadProfileInfo,
loadAvatar,
loadCover,
- loadFollowers,
- loadFollowing,
+ loadFriends,
+ loadMomentCategories,
loadMoments,
} from '../../services';
@@ -64,15 +62,9 @@ export const loadUserX = (
payload: {screenType, userId, data},
}),
);
- loadFollowers(userId, token).then((data) =>
+ loadFriends(userId, token).then((data) =>
dispatch({
- type: userXFollowersFetched.type,
- payload: {screenType, userId, data},
- }),
- );
- loadFollowing(userId, token).then((data) =>
- dispatch({
- type: userXFollowingFetched.type,
+ type: userXFriendsFetched.type,
payload: {screenType, userId, data},
}),
);
@@ -93,7 +85,7 @@ export const loadUserX = (
}
};
-export const updateUserXFollowersAndFollowing = (
+export const updateUserXFriends = (
userId: string,
state: RootState,
): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
@@ -104,15 +96,9 @@ export const updateUserXFollowersAndFollowing = (
const token = await getTokenOrLogout(dispatch);
screens.forEach((screenType) => {
if (userXInStore(state, screenType, userId)) {
- loadFollowers(userId, token).then((data) =>
- dispatch({
- type: userXFollowersFetched.type,
- payload: {screenType, userId, data},
- }),
- );
- loadFollowing(userId, token).then((data) =>
+ loadFriends(userId, token).then((data) =>
dispatch({
- type: userXFollowingFetched.type,
+ type: userXFriendsFetched.type,
payload: {screenType, userId, data},
}),
);
diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts
index b75569d6..883c0487 100644
--- a/src/store/initialStates.ts
+++ b/src/store/initialStates.ts
@@ -16,6 +16,7 @@ export const NO_PROFILE: ProfileType = {
name: '',
gender: '',
birthday: undefined,
+ university_class: 2021,
snapchat: '',
tiktok: '',
};
@@ -38,13 +39,12 @@ export const NO_USER_DATA = {
cover: <string | null>'',
};
-export const NO_NOTIFICATIONS = {
- notifications: EMPTY_NOTIFICATIONS_LIST,
+export const NO_FRIENDS_DATA = {
+ friends: EMPTY_PROFILE_PREVIEW_LIST,
};
-export const NO_FOLLOW_DATA = {
- followers: EMPTY_PROFILE_PREVIEW_LIST,
- following: EMPTY_PROFILE_PREVIEW_LIST,
+export const NO_NOTIFICATIONS = {
+ notifications: EMPTY_NOTIFICATIONS_LIST,
};
export const NO_MOMENTS = {
@@ -97,8 +97,7 @@ export const DUMMY_USERID = 'ID-1234-567';
export const DUMMY_USERNAME = 'tagg_userX';
export const EMPTY_USER_X = <UserXType>{
- followers: EMPTY_PROFILE_PREVIEW_LIST,
- following: EMPTY_PROFILE_PREVIEW_LIST,
+ friends: EMPTY_PROFILE_PREVIEW_LIST,
moments: EMPTY_MOMENTS_LIST,
momentCategories: MOMENT_CATEGORIES_MAP,
socialAccounts: NO_SOCIAL_ACCOUNTS,
diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts
index f525eb81..c9463639 100644
--- a/src/store/reducers/index.ts
+++ b/src/store/reducers/index.ts
@@ -1,4 +1,4 @@
-export * from './userFollowReducer';
+export * from './userFriendsReducer';
export * from './userReducer';
export * from './userMomentsReducer';
export * from './userSocialsReducer';
diff --git a/src/store/reducers/userFollowReducer.ts b/src/store/reducers/userFollowReducer.ts
deleted file mode 100644
index 55e16532..00000000
--- a/src/store/reducers/userFollowReducer.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import {createSlice} from '@reduxjs/toolkit';
-import {act} from 'react-test-renderer';
-import {NO_FOLLOW_DATA} from '../initialStates';
-
-const userFollowSlice = createSlice({
- name: 'userFollow',
- initialState: NO_FOLLOW_DATA,
- reducers: {
- userFollowFetched: (state, action) => {
- state.followers = action.payload.followers;
- state.following = action.payload.following;
- },
-
- updateFollowing: (state, action) => {
- const {isFollowed, data} = action.payload;
- if (!isFollowed) state.following.push(data);
- else {
- state.following = state.following.filter(
- (follow) => follow.username !== data.username,
- );
- }
- },
- },
-});
-
-export const {userFollowFetched, updateFollowing} = userFollowSlice.actions;
-export const userFollowReducer = userFollowSlice.reducer;
diff --git a/src/store/reducers/userFriendsReducer.ts b/src/store/reducers/userFriendsReducer.ts
new file mode 100644
index 00000000..2041a181
--- /dev/null
+++ b/src/store/reducers/userFriendsReducer.ts
@@ -0,0 +1,25 @@
+import {createSlice} from '@reduxjs/toolkit';
+import {NO_FRIENDS_DATA} from '../initialStates';
+
+const userFriendsSlice = createSlice({
+ name: 'userFriends',
+ initialState: NO_FRIENDS_DATA,
+ reducers: {
+ userFriendsFetched: (state, action) => {
+ state.friends = action.payload.friends;
+ },
+
+ updateFriends: (state, action) => {
+ const {isFriend, data} = action.payload;
+ if (!isFriend) state.friends.push(data);
+ else {
+ state.friends = state.friends.filter(
+ (friend) => friend.username !== data.username,
+ );
+ }
+ },
+ },
+});
+
+export const {userFriendsFetched, updateFriends} = userFriendsSlice.actions;
+export const userFriendsReducer = userFriendsSlice.reducer;
diff --git a/src/store/reducers/userXReducer.ts b/src/store/reducers/userXReducer.ts
index bb142864..fa1598b2 100644
--- a/src/store/reducers/userXReducer.ts
+++ b/src/store/reducers/userXReducer.ts
@@ -38,16 +38,10 @@ const userXSlice = createSlice({
].moments = action.payload.data;
},
- userXFollowersFetched: (state, action) => {
+ userXFriendsFetched: (state, action) => {
state[<ScreenType>action.payload.screenType][
action.payload.userId
- ].followers = action.payload.data;
- },
-
- userXFollowingFetched: (state, action) => {
- state[<ScreenType>action.payload.screenType][
- action.payload.userId
- ].following = action.payload.data;
+ ].friends = action.payload.data;
},
userXAvatarFetched: (state, action) => {
@@ -80,8 +74,7 @@ export const {
userXUserFetched,
userXRequested,
userXAvatarFetched,
- userXFollowersFetched,
- userXFollowingFetched,
+ userXFriendsFetched,
userXCoverFetched,
userXMomentsFetched,
userXProfileFetched,
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 7940b1fe..68a5c67c 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -2,7 +2,7 @@ import {combineReducers} from 'redux';
import {
userDataReducer,
userSocialsReducer,
- userFollowReducer,
+ userFriendsReducer,
userMomentsReducer,
taggUsersReducer,
userBlockReducer,
@@ -17,7 +17,7 @@ import {
const rootReducer = combineReducers({
user: userDataReducer,
- follow: userFollowReducer,
+ friends: userFriendsReducer,
moments: userMomentsReducer,
notifications: userNotificationsReducer,
socialAccounts: userSocialsReducer,
diff --git a/src/types/types.ts b/src/types/types.ts
index fc0af522..471f5b84 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -18,6 +18,7 @@ export interface ProfileType {
biography: string;
website: string;
gender: string;
+ university_class: number;
birthday: Date | undefined;
snapchat: string;
tiktok: string;
@@ -93,7 +94,7 @@ export type PreviewType =
| 'Search'
| 'Recent'
| 'Discover Users'
- | 'Follow';
+ | 'Friend';
export enum ScreenType {
Profile,
@@ -109,8 +110,7 @@ export enum ScreenType {
* We reset information on this record as soon as the stack corresponding to the screen is reset.
*/
export interface UserXType {
- followers: ProfilePreviewType[];
- following: ProfilePreviewType[];
+ friends: ProfilePreviewType[];
moments: MomentType[];
socialAccounts: Record<string, SocialAccountType>;
momentCategories: Record<MomentCategoryType, boolean>;
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 27411149..a2f88e8b 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -3,12 +3,12 @@ import {Linking} from 'react-native';
import {BROWSABLE_SOCIAL_URLS, TOGGLE_BUTTON_TYPE} from '../constants';
export const getToggleButtonText: (
- button_type: string,
+ buttonType: string,
state: boolean,
-) => string | null = (button_type, state) => {
- switch (button_type) {
- case TOGGLE_BUTTON_TYPE.FOLLOW_UNFOLLOW:
- return state ? 'Unfollow' : 'Follow';
+) => string | null = (buttonType, state) => {
+ switch (buttonType) {
+ case TOGGLE_BUTTON_TYPE.FRIEND_UNFRIEND:
+ return state ? 'Unfriend' : 'Add Friend';
case TOGGLE_BUTTON_TYPE.BLOCK_UNBLOCK:
return state ? 'Unblock' : 'Block';
default:
@@ -25,6 +25,11 @@ export const handleOpenSocialUrlOnBrowser = (
}
};
+//Returns university class just like we would like to display on profile page
+export const getUniversityClass = (universityClass: number) => {
+ return `Class of ${(universityClass % 2000).toString()}'`;
+};
+
export const getDateAge: (
date: moment.Moment,
) => 'today' | 'yesterday' | 'thisWeek' | 'unknown' = (date: moment.Moment) => {
diff --git a/src/utils/users.ts b/src/utils/users.ts
index be92d184..bcb43cbc 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -4,7 +4,7 @@ import {loadSocialPosts} from '../services';
import {
loadAllSocials,
loadBlockedList,
- loadFollowData,
+ loadFriendsData,
loadRecentlySearched,
loadUserData,
loadUserMoments,
@@ -21,7 +21,7 @@ import {ScreenType, UserType} from './../types/types';
const loadData = async (dispatch: AppDispatch, user: UserType) => {
await Promise.all([
dispatch(loadUserData(user)),
- dispatch(loadFollowData(user.userId)),
+ dispatch(loadFriendsData(user.userId)),
dispatch(loadUserMomentCategories(user.userId)),
dispatch(loadUserMoments(user.userId)),
dispatch(loadUserNotifications()),