aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-06-09 15:40:08 -0400
committerIvan Chen <ivan@tagg.id>2021-06-09 15:40:08 -0400
commitcd6e9ba609cfdbcad1365c8589e2c98d755752ad (patch)
tree98b1e947f4ae4e306f8289e26354fb783c5ee5b5
parent9d7e900a89f343f7752457956f8e1d205774b910 (diff)
parent946b1be53189487e860f37e1b422c69bb44cf0c8 (diff)
Merge branch 'master' into tma872-purple-indicator
# Conflicts: # src/constants/constants.ts # src/utils/common.ts
-rw-r--r--src/assets/icons/plus-icon-thin.svg1
-rw-r--r--src/assets/icons/plus-icon-white.svg (renamed from src/assets/icons/plus_icon-02.svg)0
-rw-r--r--src/assets/icons/plus-icon.svg (renamed from src/assets/icons/plus_icon-01.svg)0
-rw-r--r--src/components/common/BadgeDetailView.tsx71
-rw-r--r--src/components/moments/Moment.tsx4
-rw-r--r--src/components/profile/ProfileBadges.tsx148
-rw-r--r--src/components/profile/ProfileBody.tsx3
-rw-r--r--src/components/profile/index.ts1
-rw-r--r--src/components/suggestedPeople/BadgeIcon.tsx19
-rw-r--r--src/components/suggestedPeople/index.ts1
-rw-r--r--src/components/suggestedPeople/legacy/BadgesDropdown.tsx13
-rw-r--r--src/components/suggestedPeople/legacy/SPTaggsBar.tsx (renamed from src/components/suggestedPeople/SPTaggsBar.tsx)14
-rw-r--r--src/components/taggs/Tagg.tsx56
-rw-r--r--src/components/taggs/TaggsBar.tsx2
-rw-r--r--src/constants/constants.ts1
-rw-r--r--src/constants/regex.ts2
-rw-r--r--src/screens/profile/CategorySelection.tsx2
-rw-r--r--src/screens/profile/EditProfile.tsx15
-rw-r--r--src/screens/suggestedPeople/SPBody.tsx52
-rw-r--r--src/store/actions/user.ts23
-rw-r--r--src/store/initialStates.ts4
-rw-r--r--src/store/reducers/userReducer.ts8
-rw-r--r--src/types/types.ts16
-rw-r--r--src/utils/common.ts42
-rw-r--r--src/utils/users.ts26
25 files changed, 335 insertions, 189 deletions
diff --git a/src/assets/icons/plus-icon-thin.svg b/src/assets/icons/plus-icon-thin.svg
new file mode 100644
index 00000000..1a582474
--- /dev/null
+++ b/src/assets/icons/plus-icon-thin.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216 216"><defs><style>.cls-1{fill:none;stroke-miterlimit:10;stroke-width:7.33px;}}</style></defs><circle class="cls-1" cx="108.05" cy="108.21" r="103.73" stroke="currentColor"/><rect class="cls-2" x="99.56" y="48.48" width="16.99" height="119.46" rx="7.75" fill="currentColor"/><rect class="cls-2" x="99.56" y="48.48" width="16.99" height="119.46" rx="7.75" transform="translate(-0.16 216.26) rotate(-90)" fill="currentColor"/></svg> \ No newline at end of file
diff --git a/src/assets/icons/plus_icon-02.svg b/src/assets/icons/plus-icon-white.svg
index 25527911..25527911 100644
--- a/src/assets/icons/plus_icon-02.svg
+++ b/src/assets/icons/plus-icon-white.svg
diff --git a/src/assets/icons/plus_icon-01.svg b/src/assets/icons/plus-icon.svg
index 7a3b21d2..7a3b21d2 100644
--- a/src/assets/icons/plus_icon-01.svg
+++ b/src/assets/icons/plus-icon.svg
diff --git a/src/components/common/BadgeDetailView.tsx b/src/components/common/BadgeDetailView.tsx
index 6504300c..19f1e74d 100644
--- a/src/components/common/BadgeDetailView.tsx
+++ b/src/components/common/BadgeDetailView.tsx
@@ -5,11 +5,15 @@ import {TouchableOpacity} from 'react-native-gesture-handler';
import LinearGradient from 'react-native-linear-gradient';
import {useDispatch, useSelector} from 'react-redux';
import CloseIcon from '../../assets/ionicons/close-outline.svg';
-import {BADGE_GRADIENT_FIRST} from '../../constants';
-import {BADGE_DATA} from '../../constants/badges';
+import {BADGE_GRADIENT_FIRST, BADGE_LIMIT} from '../../constants';
+import {removeUserBadge} from '../../store/actions';
import {RootState} from '../../store/rootreducer';
-import {ScreenType} from '../../types';
-import {getUniversityBadge, normalize, removeUserBadge} from '../../utils';
+import {ScreenType, UniversityBadgeDisplayType} from '../../types';
+import {
+ badgesToDisplayBadges,
+ getUniversityBadge,
+ normalize,
+} from '../../utils';
interface BadgeDetailModalProps {
userXId: string | undefined;
@@ -34,35 +38,24 @@ const BadgeDetailView: React.FC<BadgeDetailModalProps> = ({
userXId ? state.userX[screenType][userXId] : state.user,
);
const navigation = useNavigation();
- const [selectedBadgesWithImage, setSelectedBadgesWithImage] = useState<any[]>(
- [],
- );
+ const [displayBadges, setDisplayBadges] = useState<
+ UniversityBadgeDisplayType[]
+ >([]);
+ const atLimit = badges.length >= BADGE_LIMIT;
useEffect(() => {
- let badgesWithImage = [];
- badges.forEach((e) => {
- const uniData = BADGE_DATA[e.university];
- const categoryData = uniData.filter((u) => {
- return u.title === e.category;
- });
- const badgeData = categoryData[0].data.filter((c) => {
- return c.badgeName === e.name;
- });
- badgeData.forEach((c) => {
- const obj = {...e, badgeImage: c.badgeImage};
- badgesWithImage.push(obj);
- });
- });
- setTimeout(() => {
- setSelectedBadgesWithImage(badgesWithImage);
- }, 250);
+ setDisplayBadges(badgesToDisplayBadges(badges, university));
}, [badges]);
const removeBadgeCell = async (badgeName: string) => {
- await removeUserBadge(badges, badgeName, user.userId, dispatch);
+ dispatch(removeUserBadge(badgeName, user.userId));
};
- const badgeEditCell = ({item: {id, name, badgeImage}}) => {
+ const badgeEditCell = ({
+ item: {id, name, img},
+ }: {
+ item: UniversityBadgeDisplayType;
+ }) => {
return (
<TouchableOpacity
style={styles.badgeCellContainerStyles}
@@ -71,7 +64,7 @@ const BadgeDetailView: React.FC<BadgeDetailModalProps> = ({
navigation.navigate('MutualBadgeHolders', {
badge_id: id,
badge_title: name,
- badge_img: badgeImage,
+ badge_img: img,
});
}}>
<View
@@ -88,7 +81,7 @@ const BadgeDetailView: React.FC<BadgeDetailModalProps> = ({
<Image
resizeMode="cover"
style={styles.badgeImageStyles}
- source={badgeImage}
+ source={img}
/>
</LinearGradient>
{isEditable && (
@@ -121,10 +114,20 @@ const BadgeDetailView: React.FC<BadgeDetailModalProps> = ({
};
const modalHeader = () => {
- const heading = isEditable ? 'Edit your badges!' : userFullName;
- const subheading = isEditable
- ? 'Add or delete your badges'
- : 'View badges to discover groups!';
+ let heading = '';
+ let subheading = '';
+ if (isEditable) {
+ if (atLimit) {
+ heading = 'You have reached your badge limit';
+ subheading = 'Remove a badge if you wish to add more';
+ } else {
+ heading = 'Edit your badges!';
+ subheading = 'Add or delete your badges';
+ }
+ } else {
+ heading = userFullName!;
+ subheading = 'View badges to discover groups!';
+ }
return (
<View>
<Text style={styles.modalHeadingStyles}>{heading}</Text>
@@ -155,7 +158,7 @@ const BadgeDetailView: React.FC<BadgeDetailModalProps> = ({
<FlatList
contentContainerStyle={styles.modalListStyles}
scrollEnabled={false}
- data={selectedBadgesWithImage}
+ data={displayBadges}
numColumns={3}
renderItem={badgeEditCell}
keyExtractor={(item) => item.id.toString()}
@@ -228,6 +231,8 @@ const styles = StyleSheet.create({
fontSize: normalize(17),
lineHeight: normalize(20.29),
textAlign: 'center',
+ marginVertical: normalize(10),
+ marginTop: normalize(20),
},
modalSubheadingStyles: {
fontWeight: '600',
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
index 0ceb8542..cde5b2e0 100644
--- a/src/components/moments/Moment.tsx
+++ b/src/components/moments/Moment.tsx
@@ -7,8 +7,8 @@ import ImagePicker from 'react-native-image-crop-picker';
import LinearGradient from 'react-native-linear-gradient';
import DeleteIcon from '../../assets/icons/delete-logo.svg';
import DownIcon from '../../assets/icons/down_icon.svg';
-import PlusIcon from '../../assets/icons/plus_icon-01.svg';
-import BigPlusIcon from '../../assets/icons/plus_icon-02.svg';
+import PlusIcon from '../../assets/icons/plus-icon.svg';
+import BigPlusIcon from '../../assets/icons/plus-icon-white.svg';
import UpIcon from '../../assets/icons/up_icon.svg';
import {TAGG_LIGHT_BLUE} from '../../constants';
import {ERROR_UPLOAD} from '../../constants/strings';
diff --git a/src/components/profile/ProfileBadges.tsx b/src/components/profile/ProfileBadges.tsx
new file mode 100644
index 00000000..8e68dc46
--- /dev/null
+++ b/src/components/profile/ProfileBadges.tsx
@@ -0,0 +1,148 @@
+import {useNavigation} from '@react-navigation/core';
+import React, {FC, useEffect, useState} from 'react';
+import {StyleSheet, Text, View} from 'react-native';
+import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler';
+import {useSelector} from 'react-redux';
+import {BadgeIcon} from '..';
+import PlusIconImage from '../../assets/icons/plus-icon-thin.svg';
+import {BADGE_LIMIT} from '../../constants';
+import {RootState} from '../../store/rootReducer';
+import {ScreenType, UniversityBadgeDisplayType} from '../../types';
+import {badgesToDisplayBadges, normalize} from '../../utils';
+import BadgeDetailView from '../common/BadgeDetailView';
+
+interface ProfileBadgesProps {
+ userXId: string | undefined;
+ screenType: ScreenType;
+}
+
+const ProfileBadges: React.FC<ProfileBadgesProps> = ({userXId, screenType}) => {
+ const navigation = useNavigation();
+ const {badges, name, university} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId].profile : state.user.profile,
+ );
+ const [displayBadges, setDisplayBadges] = useState<
+ UniversityBadgeDisplayType[]
+ >([]);
+ const [isEditBadgeModalVisible, setIsEditBadgeModalVisible] = useState(false);
+ const isOwnProfile = userXId === undefined;
+
+ useEffect(() => {
+ setDisplayBadges(badgesToDisplayBadges(badges, university));
+ }, [badges]);
+
+ const PlusIcon: FC = () => (
+ <TouchableOpacity
+ onPress={() => navigation.navigate('BadgeSelection', {editing: true})}>
+ <PlusIconImage style={styles.plus} />
+ </TouchableOpacity>
+ );
+
+ const CloseIcon: FC = () => (
+ <TouchableOpacity onPress={() => setIsEditBadgeModalVisible(true)}>
+ <PlusIconImage style={styles.close} />
+ </TouchableOpacity>
+ );
+
+ return (
+ <>
+ {/* Tutorial text */}
+ {displayBadges.length === 0 && isOwnProfile && (
+ <>
+ <Text style={styles.title}>Badges</Text>
+ <Text style={styles.body}>
+ Proudly represent your team, club, or organization!
+ </Text>
+ </>
+ )}
+ {displayBadges.length === 0 && isOwnProfile && (
+ // Grey circle placeholders
+ <ScrollView
+ contentContainerStyle={styles.badgeContainer}
+ scrollEnabled={false}
+ horizontal>
+ <PlusIcon />
+ {Array(BADGE_LIMIT)
+ .fill(0)
+ .map(() => (
+ <View style={[styles.grey, styles.circle]} />
+ ))}
+ </ScrollView>
+ )}
+ {displayBadges.length !== 0 && (
+ // Populating actual badges
+ <ScrollView
+ contentContainerStyle={styles.badgeContainer}
+ scrollEnabled={false}
+ horizontal>
+ {/* Actual badges */}
+ {displayBadges.map((displayBadge) => (
+ <BadgeIcon key={displayBadge.id} badge={displayBadge} />
+ ))}
+ {/* Plus icon */}
+ {displayBadges.length < BADGE_LIMIT && isOwnProfile && <PlusIcon />}
+ {/* Empty placeholders for space-between styling */}
+ {Array(BADGE_LIMIT + 1)
+ .fill(0)
+ .splice(displayBadges.length + 1, BADGE_LIMIT)
+ .map(() => (
+ <View style={styles.circle} />
+ ))}
+ {/* X button */}
+ {displayBadges.length === BADGE_LIMIT && isOwnProfile && (
+ <CloseIcon />
+ )}
+ </ScrollView>
+ )}
+ {isEditBadgeModalVisible && (
+ <BadgeDetailView
+ userXId={userXId}
+ screenType={screenType}
+ isEditable={isOwnProfile}
+ userFullName={name}
+ setBadgeViewVisible={setIsEditBadgeModalVisible}
+ />
+ )}
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ title: {
+ fontWeight: '600',
+ fontSize: normalize(13.5),
+ lineHeight: normalize(18),
+ },
+ body: {
+ fontSize: normalize(13.5),
+ lineHeight: normalize(17),
+ marginBottom: 10,
+ },
+ badgeContainer: {
+ width: '100%',
+ justifyContent: 'space-between',
+ marginTop: 10,
+ marginBottom: 15,
+ },
+ circle: {
+ width: normalize(31),
+ height: normalize(31),
+ borderRadius: normalize(31) / 2,
+ },
+ grey: {
+ backgroundColor: '#c4c4c4',
+ },
+ plus: {
+ width: normalize(31),
+ height: normalize(31),
+ color: 'black',
+ },
+ close: {
+ width: normalize(31),
+ height: normalize(31),
+ color: 'grey',
+ transform: [{rotate: '45deg'}],
+ },
+});
+
+export default ProfileBadges;
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index 8743acfb..c0ee508a 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -20,6 +20,7 @@ import {
import {canViewProfile} from '../../utils/users';
import {FriendsButton} from '../common';
import {MessageButton} from '../messages';
+import ProfileBadges from './ProfileBadges';
import ToggleButton from './ToggleButton';
interface ProfileBodyProps {
@@ -65,6 +66,7 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
return (
<View onLayout={onLayout} style={styles.container}>
+ <ProfileBadges {...{userXId, screenType}} />
<Text style={styles.username}>{`@${username}`}</Text>
{biography.length > 0 && (
<Text style={styles.biography}>{`${biography}`}</Text>
@@ -137,7 +139,6 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
},
container: {
- paddingVertical: '1%',
paddingHorizontal: 18,
backgroundColor: 'white',
},
diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts
index c544c3f2..faf273d9 100644
--- a/src/components/profile/index.ts
+++ b/src/components/profile/index.ts
@@ -9,3 +9,4 @@ export {default as ProfileMoreInfoDrawer} from './ProfileMoreInfoDrawer';
export {default as MomentMoreInfoDrawer} from './MomentMoreInfoDrawer';
export {default as UniversityIcon} from './UniversityIcon';
export {default as TaggAvatar} from './TaggAvatar';
+export {default as ProfileBadges} from './ProfileBadges';
diff --git a/src/components/suggestedPeople/BadgeIcon.tsx b/src/components/suggestedPeople/BadgeIcon.tsx
index 8f576a43..616bac93 100644
--- a/src/components/suggestedPeople/BadgeIcon.tsx
+++ b/src/components/suggestedPeople/BadgeIcon.tsx
@@ -1,24 +1,17 @@
import {useNavigation} from '@react-navigation/core';
import React from 'react';
-import {
- Image,
- ImageSourcePropType,
- StyleProp,
- StyleSheet,
- ViewStyle,
-} from 'react-native';
+import {Image, StyleProp, StyleSheet, ViewStyle} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import LinearGradient from 'react-native-linear-gradient';
-import {UniversityBadge} from '../../types';
+import {UniversityBadgeDisplayType} from '../../types';
import {normalize} from '../../utils';
interface BadgeIconProps {
- badge: UniversityBadge;
- img: ImageSourcePropType;
+ badge: UniversityBadgeDisplayType;
style?: StyleProp<ViewStyle>;
}
-const BadgeIcon: React.FC<BadgeIconProps> = ({badge, img, style}) => {
+const BadgeIcon: React.FC<BadgeIconProps> = ({badge, style}) => {
const navigation = useNavigation();
return (
<TouchableOpacity
@@ -27,7 +20,7 @@ const BadgeIcon: React.FC<BadgeIconProps> = ({badge, img, style}) => {
navigation.navigate('MutualBadgeHolders', {
badge_id: badge.id,
badge_title: badge.name,
- badge_img: img,
+ badge_img: badge.img,
});
}}>
<LinearGradient
@@ -36,7 +29,7 @@ const BadgeIcon: React.FC<BadgeIconProps> = ({badge, img, style}) => {
angle={154.72}
angleCenter={{x: 0.5, y: 0.5}}
style={styles.badgeBackground}>
- <Image source={img} style={styles.icon} />
+ <Image source={badge.img} style={styles.icon} />
</LinearGradient>
</TouchableOpacity>
);
diff --git a/src/components/suggestedPeople/index.ts b/src/components/suggestedPeople/index.ts
index 34bb96d4..ecdf4f35 100644
--- a/src/components/suggestedPeople/index.ts
+++ b/src/components/suggestedPeople/index.ts
@@ -1,3 +1,2 @@
export {default as MutualFriends} from './MutualFriends';
-export {default as SPTaggsBar} from './SPTaggsBar';
export {default as BadgeIcon} from './BadgeIcon';
diff --git a/src/components/suggestedPeople/legacy/BadgesDropdown.tsx b/src/components/suggestedPeople/legacy/BadgesDropdown.tsx
index 267355f3..2c177e69 100644
--- a/src/components/suggestedPeople/legacy/BadgesDropdown.tsx
+++ b/src/components/suggestedPeople/legacy/BadgesDropdown.tsx
@@ -1,18 +1,15 @@
import React, {useEffect, useState} from 'react';
-import {ImageSourcePropType, StyleSheet} from 'react-native';
+import {StyleSheet} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Animated, {Easing} from 'react-native-reanimated';
import {BadgeIcon, UniversityIcon} from '../..';
-import {UniversityBadge, UniversityType} from '../../../types';
+import {UniversityBadgeDisplayType, UniversityType} from '../../../types';
import {normalize} from '../../../utils';
import UniversityIconClicked from '../UniversityIconClicked';
interface BadgesDropdownProps {
university: UniversityType;
- localBadges: {
- badge: UniversityBadge;
- img: ImageSourcePropType;
- }[];
+ localBadges: UniversityBadgeDisplayType[];
}
const BadgesDropdown: React.FC<BadgesDropdownProps> = ({
@@ -92,7 +89,7 @@ const BadgesDropdown: React.FC<BadgesDropdownProps> = ({
)}
</TouchableOpacity>
{localBadges &&
- localBadges.map(({badge, img}, index) => (
+ localBadges.map((badge, index) => (
<Animated.View
key={badge.id}
style={[
@@ -102,7 +99,7 @@ const BadgesDropdown: React.FC<BadgesDropdownProps> = ({
zIndex: -1 * badge.id,
},
]}>
- <BadgeIcon badge={badge} img={img} />
+ <BadgeIcon badge={badge} />
</Animated.View>
))}
</Animated.View>
diff --git a/src/components/suggestedPeople/SPTaggsBar.tsx b/src/components/suggestedPeople/legacy/SPTaggsBar.tsx
index 3ab33da1..3273f88d 100644
--- a/src/components/suggestedPeople/SPTaggsBar.tsx
+++ b/src/components/suggestedPeople/legacy/SPTaggsBar.tsx
@@ -2,12 +2,12 @@ import React, {useEffect, useState} from 'react';
import {StyleSheet} from 'react-native';
import Animated from 'react-native-reanimated';
import {useDispatch, useSelector} from 'react-redux';
-import {INTEGRATED_SOCIAL_LIST, SOCIAL_LIST} from '../../constants';
-import {getLinkedSocials} from '../../services';
-import {loadIndividualSocial, updateSocial} from '../../store/actions';
-import {RootState} from '../../store/rootReducer';
-import {ScreenType} from '../../types';
-import Tagg from '../taggs/Tagg';
+import {INTEGRATED_SOCIAL_LIST, SOCIAL_LIST} from '../../../constants';
+import {getLinkedSocials} from '../../../services';
+import {loadIndividualSocial, updateSocial} from '../../../store/actions';
+import {RootState} from '../../../store/rootReducer';
+import {ScreenType} from '../../../types';
+import Tagg from '../../taggs/Tagg';
const {View, ScrollView} = Animated;
interface TaggsBarProps {
@@ -66,7 +66,6 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
setTaggsNeedUpdate={setTaggsNeedUpdate}
setSocialDataNeedUpdate={handleSocialUpdate}
- whiteRing={true}
screenType={screenType}
/>,
);
@@ -84,7 +83,6 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
setSocialDataNeedUpdate={handleSocialUpdate}
userXId={userXId}
user={user}
- whiteRing={true}
screenType={screenType}
/>,
);
diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx
index 4a58bacb..0e0311c5 100644
--- a/src/components/taggs/Tagg.tsx
+++ b/src/components/taggs/Tagg.tsx
@@ -5,13 +5,8 @@ import {useSelector} from 'react-redux';
import PurpleRingPlus from '../../assets/icons/purple_ring+.svg';
import PurpleRing from '../../assets/icons/purple_ring.svg';
import RingPlus from '../../assets/icons/ring+.svg';
-import WhiteRing from '../../assets/icons/ring-white.svg';
import Ring from '../../assets/icons/ring.svg';
-import {
- INTEGRATED_SOCIAL_LIST,
- SOCIAL_ICON_SIZE_ADJUSTMENT,
- TAGG_RING_DIM,
-} from '../../constants';
+import {INTEGRATED_SOCIAL_LIST, TAGG_RING_DIM} from '../../constants';
import {
ERROR_LINK,
ERROR_UNABLE_TO_FIND_PROFILE,
@@ -24,8 +19,8 @@ import {
} from '../../services';
import {RootState} from '../../store/rootReducer';
import {ScreenType, UserType} from '../../types';
-import {canViewProfile, normalize} from '../../utils';
-import {SmallSocialIcon, SocialIcon, SocialLinkModal} from '../common';
+import {canViewProfile} from '../../utils';
+import {SocialIcon, SocialLinkModal} from '../common';
interface TaggProps {
social: string;
@@ -35,7 +30,6 @@ interface TaggProps {
setSocialDataNeedUpdate: (social: string, username: string) => void;
userXId: string | undefined;
user: UserType;
- whiteRing: boolean | undefined;
screenType: ScreenType;
}
@@ -48,7 +42,6 @@ const Tagg: React.FC<TaggProps> = ({
userXId,
screenType,
user,
- whiteRing,
}) => {
const navigation = useNavigation();
const state = useSelector((s: RootState) => s);
@@ -107,9 +100,6 @@ const Tagg: React.FC<TaggProps> = ({
const pickTheRightRingHere = () => {
if (youMayPass) {
- if (whiteRing) {
- return <WhiteRing width={TAGG_RING_DIM} height={TAGG_RING_DIM} />;
- }
if (social === 'Tagg') {
return <Ring width={TAGG_RING_DIM} height={TAGG_RING_DIM} />;
} else {
@@ -139,7 +129,7 @@ const Tagg: React.FC<TaggProps> = ({
return (
<>
- {(userXId && !isLinked) || (whiteRing && !userXId) ? (
+ {userXId && !isLinked ? (
<Fragment />
) : (
<>
@@ -149,27 +139,13 @@ const Tagg: React.FC<TaggProps> = ({
setModalVisible={setModalVisible}
completionCallback={linkNonIntegratedSocial}
/>
- <View style={whiteRing ? styles.spcontainer : styles.container}>
+ <View style={styles.container}>
<TouchableOpacity
style={styles.iconTap}
onPress={modalOrAuthBrowserOrPass}>
<SocialIcon style={styles.icon} social={social} whiteRing />
{pickTheRightRingHere()}
</TouchableOpacity>
- {!whiteRing && (
- <View style={styles.smallIconContainer}>
- <SmallSocialIcon
- style={[
- styles.smallIcon,
- {
- height: SOCIAL_ICON_SIZE_ADJUSTMENT[social],
- width: SOCIAL_ICON_SIZE_ADJUSTMENT[social],
- },
- ]}
- social={social}
- />
- </View>
- )}
</View>
</>
)}
@@ -178,19 +154,10 @@ const Tagg: React.FC<TaggProps> = ({
};
const styles = StyleSheet.create({
- spcontainer: {
- justifyContent: 'space-between',
- alignItems: 'center',
- marginRight: 15,
- marginLeft: 19,
- height: normalize(60),
- },
container: {
justifyContent: 'space-between',
alignItems: 'center',
- marginRight: 15,
- marginLeft: 15,
- height: normalize(90),
+ marginHorizontal: 15,
},
iconTap: {
justifyContent: 'center',
@@ -202,17 +169,6 @@ const styles = StyleSheet.create({
borderRadius: 30,
position: 'absolute',
},
- smallIconContainer: {
- height: 20,
- width: 20,
- position: 'absolute',
- justifyContent: 'center',
- alignItems: 'center',
- bottom: 0,
- },
- smallIcon: {
- borderRadius: 1000,
- },
});
export default Tagg;
diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx
index a7e8fc7a..7076ffaf 100644
--- a/src/components/taggs/TaggsBar.tsx
+++ b/src/components/taggs/TaggsBar.tsx
@@ -82,7 +82,6 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
setTaggsNeedUpdate={setTaggsNeedUpdate}
setSocialDataNeedUpdate={handleSocialUpdate}
- whiteRing={false}
/>,
);
i++;
@@ -100,7 +99,6 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
userXId={userXId}
screenType={screenType}
user={user}
- whiteRing={false}
/>,
);
i++;
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index e6c23554..f4ffd750 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -23,6 +23,7 @@ export const TAGG_RING_DIM = normalize(60);
// default height of the navigation bar, from react native library, unless on ipad
export const NAV_BAR_HEIGHT = 49;
+export const BADGE_LIMIT = 5;
export const INTEGRATED_SOCIAL_LIST: string[] = [
'Instagram',
diff --git a/src/constants/regex.ts b/src/constants/regex.ts
index 61523203..f934185d 100644
--- a/src/constants/regex.ts
+++ b/src/constants/regex.ts
@@ -36,7 +36,7 @@ export const nameRegex: RegExp = /^[A-Za-z'\-,. ]{2,20}$/;
* - match alphanumerics, and special characters used in URLs
*/
export const websiteRegex: RegExp =
- /^$|^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,50}\.[a-zA-Z0-9()]{2,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]{0,35})$/;
+ /^$|^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,50}\.[a-zA-Z0-9()]{2,6}\b([-a-zA-Z0-9()@:%_+.~#?&\/=]{0,35})$/;
/**
* The website regex has the following constraints
diff --git a/src/screens/profile/CategorySelection.tsx b/src/screens/profile/CategorySelection.tsx
index c02eef0d..ea443fce 100644
--- a/src/screens/profile/CategorySelection.tsx
+++ b/src/screens/profile/CategorySelection.tsx
@@ -11,7 +11,7 @@ import {
} from 'react-native';
import {ScrollView} from 'react-native-gesture-handler';
import {useDispatch, useSelector} from 'react-redux';
-import PlusIcon from '../../assets/icons/plus_icon-01.svg';
+import PlusIcon from '../../assets/icons/plus-icon.svg';
import {Background, MomentCategory} from '../../components';
import {MOMENT_CATEGORIES, TAGG_LIGHT_BLUE_2} from '../../constants';
import {ERROR_SOMETHING_WENT_WRONG} from '../../constants/strings';
diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx
index 26802e45..765bbf01 100644
--- a/src/screens/profile/EditProfile.tsx
+++ b/src/screens/profile/EditProfile.tsx
@@ -305,14 +305,13 @@ const EditProfile: React.FC<EditProfileProps> = ({route, navigation}) => {
type: 'image/jpg',
});
}
- if (form.website) {
- if (form.isValidWebsite) {
- request.append('website', form.website);
- } else {
- setForm({...form, attemptedSubmit: false});
- setTimeout(() => setForm({...form, attemptedSubmit: true}));
- invalidFields = true;
- }
+
+ if (form.isValidWebsite) {
+ request.append('website', form.website);
+ } else {
+ setForm({...form, attemptedSubmit: false});
+ setTimeout(() => setForm({...form, attemptedSubmit: true}));
+ invalidFields = true;
}
if (form.bio) {
diff --git a/src/screens/suggestedPeople/SPBody.tsx b/src/screens/suggestedPeople/SPBody.tsx
index eb80da49..fea67950 100644
--- a/src/screens/suggestedPeople/SPBody.tsx
+++ b/src/screens/suggestedPeople/SPBody.tsx
@@ -1,19 +1,24 @@
import {useNavigation} from '@react-navigation/native';
import React, {Fragment, useEffect, useMemo, useState} from 'react';
-import {ImageSourcePropType, StyleSheet, Text, View} from 'react-native';
+import {StyleSheet, Text, View} from 'react-native';
import {Image} from 'react-native-animatable';
import {TouchableOpacity} from 'react-native-gesture-handler';
import RequestedButton from '../../assets/ionicons/requested-button.svg';
import {UniversityIcon} from '../../components';
import {BadgeIcon, MutualFriends} from '../../components/suggestedPeople';
-import {BADGE_DATA} from '../../constants/badges';
import {
ProfilePreviewType,
ScreenType,
SuggestedPeopleDataType,
- UniversityBadge,
+ UniversityBadgeDisplayType,
} from '../../types';
-import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {
+ badgesToDisplayBadges,
+ isIPhoneX,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+} from '../../utils';
interface SPBodyProps {
item: SuggestedPeopleDataType;
@@ -39,30 +44,12 @@ const SPBody: React.FC<SPBodyProps> = ({
}) => {
const firstItem = itemIndex === 0;
const screenType = ScreenType.SuggestedPeople;
- const [localBadges, setLocalBadges] = useState<
- {
- badge: UniversityBadge;
- img: ImageSourcePropType;
- }[]
+ const [displayBadges, setDisplayBadges] = useState<
+ UniversityBadgeDisplayType[]
>([]);
const navigation = useNavigation();
useEffect(() => {
- const newBadges: {badge: UniversityBadge; img: any}[] = [];
- const findBadgeIcons = (badge: UniversityBadge) => {
- BADGE_DATA[university]?.forEach((item) => {
- if (item.title === badge.category) {
- item.data.forEach((object) => {
- if (object.badgeName === badge.name) {
- newBadges.push({badge, img: object.badgeImage});
- }
- });
- }
- });
- setLocalBadges(newBadges);
- };
- badges
- ? badges.forEach((badge) => findBadgeIcons(badge))
- : console.log('NO BADGES FOUND');
+ setDisplayBadges(badgesToDisplayBadges(badges, university));
}, []);
const FriendButton = () => {
@@ -131,12 +118,15 @@ const SPBody: React.FC<SPBodyProps> = ({
const Badges = () => (
// Badges aligned left and spaced as if there are 5 items
<View style={styles.badgeContainer}>
- {localBadges.map(({badge, img}, index) => (
- <BadgeIcon key={index} badge={badge} img={img} style={styles.badge} />
- ))}
- {[0, 0, 0, 0, 0].splice(localBadges.length, 5).map((_, index) => (
- <View key={index} style={styles.badge} />
+ {displayBadges.map((displayBadge, index) => (
+ <BadgeIcon key={index} badge={displayBadge} style={styles.badge} />
))}
+ {Array(5)
+ .fill(0)
+ .splice(displayBadges.length, 5)
+ .map((_, index) => (
+ <View key={index} style={styles.badge} />
+ ))}
</View>
);
@@ -159,7 +149,7 @@ const SPBody: React.FC<SPBodyProps> = ({
{user.id !== loggedInUserId && <FriendButton />}
</View>
</View>
- {localBadges.length !== 0 && <Badges />}
+ {displayBadges.length !== 0 && <Badges />}
<View style={styles.marginManager}>
<MutualFriends user={user} friends={mutual_friends} />
</View>
diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts
index 941101df..b1cb8719 100644
--- a/src/store/actions/user.ts
+++ b/src/store/actions/user.ts
@@ -4,12 +4,14 @@ import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
getProfilePic,
loadProfileInfo,
+ removeBadgesService,
sendSuggestedPeopleLinked,
} from '../../services';
import {UniversityBadge, UserType} from '../../types/types';
import {getTokenOrLogout} from '../../utils';
import {
clearHeaderAndProfileImages,
+ profileBadgeRemoved,
profileBadgesUpdated,
profileCompletionStageUpdated,
setIsOnboardedUser,
@@ -107,6 +109,27 @@ export const updateUserBadges =
}
};
+/**
+ * Removes a single badge from logged-in user by badge name.
+ * @param badgeName name of badge to be removed
+ * @param loggedInUserId userId of loggedInUser
+ */
+export const removeUserBadge =
+ (
+ badgeName: string,
+ loggedInUserId: string,
+ ): ThunkAction<Promise<void>, RootState, unknown, Action<string>> =>
+ async (dispatch) => {
+ try {
+ const success = await removeBadgesService([badgeName], loggedInUserId);
+ if (success) {
+ dispatch({type: profileBadgeRemoved.type, payload: {badge: badgeName}});
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ };
+
export const updateProfileCompletionStage =
(
stage: number,
diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts
index e0f9d776..e2902a2d 100644
--- a/src/store/initialStates.ts
+++ b/src/store/initialStates.ts
@@ -1,4 +1,4 @@
-import {CommentThreadType} from './../types/types';
+import {CommentThreadType, UniversityType} from './../types/types';
import {
MomentType,
NotificationType,
@@ -17,7 +17,7 @@ export const NO_PROFILE: ProfileInfoType = {
gender: '',
birthday: undefined,
university_class: 2021,
- university: undefined,
+ university: UniversityType.Empty,
badges: [],
//Default to an invalid value and ignore it gracefully while showing tutorials / popups.
profile_completion_stage: -1,
diff --git a/src/store/reducers/userReducer.ts b/src/store/reducers/userReducer.ts
index 97bf845c..4692c5d3 100644
--- a/src/store/reducers/userReducer.ts
+++ b/src/store/reducers/userReducer.ts
@@ -46,6 +46,12 @@ const userDataSlice = createSlice({
state.profile.badges = action.payload.badges;
},
+ profileBadgeRemoved: (state, action) => {
+ state.profile.badges = state.profile.badges.filter(
+ (badge) => badge.name !== action.payload.badge,
+ );
+ },
+
profileCompletionStageUpdated: (state, action) => {
state.profile.profile_completion_stage = action.payload.stage;
},
@@ -95,6 +101,6 @@ export const {
setSuggestedPeopleImage,
clearHeaderAndProfileImages,
profileBadgesUpdated,
- // setChatClientReady,
+ profileBadgeRemoved,
} = userDataSlice.actions;
export const userDataReducer = userDataSlice.reducer;
diff --git a/src/types/types.ts b/src/types/types.ts
index b294e3f1..e54c2201 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -1,3 +1,4 @@
+import {ImageSourcePropType} from 'react-native';
import Animated from 'react-native-reanimated';
import {Channel as ChannelType, StreamChat} from 'stream-chat';
@@ -262,6 +263,10 @@ export type UniversityBadge = {
category: string;
};
+export interface UniversityBadgeDisplayType extends UniversityBadge {
+ img: ImageSourcePropType;
+}
+
export type SuggestedPeopleDataType = {
user: ProfilePreviewType;
university: UniversityType;
@@ -291,7 +296,16 @@ export type ContactType = {
};
export type UniversityBadgeType = 'Search' | 'Crest';
-export type BadgeDataType = Record<UniversityType, any[]>;
+export type BadgeDataType = Record<
+ UniversityType,
+ {
+ title: string;
+ data: {
+ badgeName: string;
+ badgeImage: ImageSourcePropType;
+ }[];
+ }[]
+>;
// Stream Chat Types
export type LocalAttachmentType = Record<string, unknown>;
diff --git a/src/utils/common.ts b/src/utils/common.ts
index cb0bd62d..645f229a 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,11 +1,17 @@
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
-import {Linking} from 'react-native';
+import {ImageSourcePropType, Linking} from 'react-native';
import {getAll} from 'react-native-contacts';
-import {BROWSABLE_SOCIAL_URLS, TOGGLE_BUTTON_TYPE} from '../constants';
+import {
+ BADGE_DATA,
+ BROWSABLE_SOCIAL_URLS,
+ TOGGLE_BUTTON_TYPE,
+} from '../constants';
import {
ContactType,
NotificationType,
+ UniversityBadge,
+ UniversityBadgeDisplayType,
UniversityBadgeType,
UniversityType,
} from './../types/types';
@@ -198,7 +204,37 @@ export const validateImageLink = async (url: string | undefined) => {
});
};
+/**
+ * Turns a list of badges into display badges (just a badge with img) by
+ * looking up the img source from our badge asset lookup constant.
+ *
+ * WARNING: Assumes a small list of badges, complexity goes up exponentially.
+ *
+ * @param badges list of university badges
+ * @param university university of which all the badges belong
+ * @returns list of display badges
+ */
+export const badgesToDisplayBadges = (
+ badges: UniversityBadge[],
+ university: UniversityType,
+) => {
+ const badgeSet: Set<string> = new Set(badges.map((b) => b.category + b.name));
+ const badgeToImgMap: Record<string, ImageSourcePropType> = {};
+ BADGE_DATA[university].forEach((category) => {
+ category.data.forEach((badgeInfo) => {
+ const key = category.title + badgeInfo.badgeName;
+ if (badgeSet.has(key)) {
+ badgeToImgMap[key] = badgeInfo.badgeImage;
+ }
+ });
+ });
+ return <UniversityBadgeDisplayType[]>badges.map((b) => ({
+ ...b,
+ img: badgeToImgMap[b.category + b.name],
+ }));
+};
+
// Documentation: https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
export const numberWithCommas = (digits: number) => {
return digits.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
-};
+ \ No newline at end of file
diff --git a/src/utils/users.ts b/src/utils/users.ts
index 8505cde2..64ad10e9 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -1,7 +1,8 @@
-import {Alert} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
+import {Alert} from 'react-native';
+import ImagePicker from 'react-native-image-crop-picker';
import {INTEGRATED_SOCIAL_LIST} from '../constants';
-import {isUserBlocked, loadSocialPosts, removeBadgesService} from '../services';
+import {isUserBlocked, loadSocialPosts, patchEditProfile} from '../services';
import {
loadAllSocials,
loadBlockedList,
@@ -11,7 +12,6 @@ import {
loadUserMoments,
loadUserNotifications,
logout,
- updateUserBadges,
} from '../store/actions';
import {NO_SOCIAL_ACCOUNTS} from '../store/initialStates';
import {loadUserMomentCategories} from './../store/actions/momentCategories';
@@ -23,10 +23,7 @@ import {
ProfilePreviewType,
ScreenType,
UserType,
- UniversityBadge,
} from './../types/types';
-import ImagePicker from 'react-native-image-crop-picker';
-import {patchEditProfile} from '../services';
const loadData = async (dispatch: AppDispatch, user: UserType) => {
await Promise.all([
@@ -205,23 +202,6 @@ export const canViewProfile = (
return false;
};
-/* Function to call remove badge service,
- * remove selected badge from list passed in and
- * dispatch thunk action to update store
- */
-export const removeUserBadge = async (
- badges: UniversityBadge[],
- badgeName: string,
- userId: string,
- dispatch: AppDispatch,
-) => {
- const success = await removeBadgesService([badgeName], userId);
- if (success === true) {
- badges = badges.filter((badge) => badge.name !== badgeName);
- dispatch(updateUserBadges(badges));
- }
-};
-
export const navigateToProfile = async (
state: RootState,
dispatch: any,