diff options
Diffstat (limited to 'src')
36 files changed, 601 insertions, 472 deletions
diff --git a/src/assets/universities/brown.png b/src/assets/universities/brown.png Binary files differnew file mode 100644 index 00000000..1f1fd887 --- /dev/null +++ b/src/assets/universities/brown.png 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()), |