aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/assets/universities/cornell-clicked.pngbin0 -> 12362 bytes
-rw-r--r--src/assets/universities/cornell-search.pngbin0 -> 12171 bytes
-rw-r--r--src/assets/universities/cornell.pngbin0 -> 12353 bytes
-rw-r--r--src/components/onboarding/UniversitySelection.tsx97
-rw-r--r--src/components/onboarding/index.ts1
-rw-r--r--src/components/profile/ProfileMoreInfoDrawer.tsx8
-rw-r--r--src/constants/strings.ts7
-rw-r--r--src/screens/onboarding/Login.tsx29
-rw-r--r--src/screens/onboarding/OnboardingStepThree.tsx100
-rw-r--r--src/screens/profile/EditProfile.tsx38
-rw-r--r--src/services/UserProfileService.ts48
-rw-r--r--src/types/types.ts12
12 files changed, 231 insertions, 109 deletions
diff --git a/src/assets/universities/cornell-clicked.png b/src/assets/universities/cornell-clicked.png
new file mode 100644
index 00000000..d6450b29
--- /dev/null
+++ b/src/assets/universities/cornell-clicked.png
Binary files differ
diff --git a/src/assets/universities/cornell-search.png b/src/assets/universities/cornell-search.png
new file mode 100644
index 00000000..ce41e7bc
--- /dev/null
+++ b/src/assets/universities/cornell-search.png
Binary files differ
diff --git a/src/assets/universities/cornell.png b/src/assets/universities/cornell.png
new file mode 100644
index 00000000..bf15f8b2
--- /dev/null
+++ b/src/assets/universities/cornell.png
Binary files differ
diff --git a/src/components/onboarding/UniversitySelection.tsx b/src/components/onboarding/UniversitySelection.tsx
new file mode 100644
index 00000000..92bec47f
--- /dev/null
+++ b/src/components/onboarding/UniversitySelection.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import {Image, ImageSourcePropType, StyleSheet, Text, View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {UniversityType} from '../../types';
+import {normalize} from '../../utils';
+
+interface UniversitySelectionProps {
+ selected: UniversityType;
+ setSelected: (selected: UniversityType) => void;
+}
+
+const UniversitySelection: React.FC<UniversitySelectionProps> = ({
+ selected,
+ setSelected,
+}) => {
+ const crestData = [
+ {
+ imagePath: require('../../assets/universities/brown.png'),
+ title: 'Brown',
+ key: UniversityType.Brown,
+ },
+ {
+ imagePath: require('../../assets/universities/cornell.png'),
+ title: 'Cornell',
+ key: UniversityType.Cornell,
+ },
+ // {
+ // imagePath: require('../../assets/universities/harvard.png'),
+ // title: 'Harvard',
+ // key: UniversityType.Harvard,
+ // },
+ ];
+ const renderButton = (
+ imagePath: ImageSourcePropType,
+ title: string,
+ key: UniversityType,
+ ) => (
+ <TouchableOpacity
+ style={
+ selected === key ? styles.crestContainerSelected : styles.crestContainer
+ }
+ onPress={() => setSelected(key)}>
+ <Image source={imagePath} style={styles.crest} />
+ <Text style={styles.crestLabel}>{title}</Text>
+ </TouchableOpacity>
+ );
+ return (
+ <>
+ <Text style={styles.title}>University Badge</Text>
+ <View style={styles.container}>
+ {crestData.map((data) =>
+ renderButton(data.imagePath, data.title, data.key),
+ )}
+ </View>
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ title: {
+ color: 'white',
+ fontSize: normalize(15),
+ lineHeight: normalize(18),
+ fontWeight: '700',
+ marginBottom: 10,
+ },
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ marginBottom: 10,
+ },
+ crest: {
+ height: normalize(25),
+ aspectRatio: 31 / 38,
+ marginBottom: 5,
+ },
+ crestContainer: {
+ alignItems: 'center',
+ padding: 10,
+ },
+ crestContainerSelected: {
+ alignItems: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ borderRadius: 5,
+ padding: 8,
+ backgroundColor: '#fff2',
+ },
+ crestLabel: {
+ color: 'white',
+ fontSize: normalize(15),
+ lineHeight: normalize(18),
+ fontWeight: '500',
+ },
+});
+
+export default UniversitySelection;
diff --git a/src/components/onboarding/index.ts b/src/components/onboarding/index.ts
index b790933f..fdb85090 100644
--- a/src/components/onboarding/index.ts
+++ b/src/components/onboarding/index.ts
@@ -10,3 +10,4 @@ export {default as TaggDropDown} from './TaggDropDown';
export {default as SocialMediaLinker} from './SocialMediaLinker';
export {default as LinkSocialMedia} from './LinkSocialMedia';
export {default as MomentCategory} from './MomentCategory';
+export {default as UniversitySelection} from './UniversitySelection';
diff --git a/src/components/profile/ProfileMoreInfoDrawer.tsx b/src/components/profile/ProfileMoreInfoDrawer.tsx
index 2fec5cca..a77a2e84 100644
--- a/src/components/profile/ProfileMoreInfoDrawer.tsx
+++ b/src/components/profile/ProfileMoreInfoDrawer.tsx
@@ -24,11 +24,9 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
const {setIsOpen, userXId, isBlocked, handleBlockUnblock, userXName} = props;
const {
user: {userId, username},
- } = useSelector((state: RootState) => state.user);
+ profile,
+ } = useSelector((state: RootState) => state?.user);
const isOwnProfile = !userXId || userXName === username;
- const {suggested_people_linked} = useSelector(
- (state: RootState) => state.user.profile,
- );
const goToEditProfile = () => {
navigation.push('EditProfile', {
@@ -39,7 +37,7 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => {
};
const goToUpdateSPProfile = () => {
- if (suggested_people_linked === 0) {
+ if (profile.suggested_people_linked === 0) {
Alert.alert(ERROR_ATTEMPT_EDIT_SP);
} else {
// Sending undefined for updatedSelectedBadges to mark that there was no update yet
diff --git a/src/constants/strings.ts b/src/constants/strings.ts
index cb442b7b..66d4a6d9 100644
--- a/src/constants/strings.ts
+++ b/src/constants/strings.ts
@@ -6,6 +6,7 @@ export const ADD_COMMENT_TEXT = (username?: string) => username ? `Reply to ${us
export const COMING_SOON_MSG = 'Creating more fun things for you, surprises coming soon πŸ˜‰';
export const ERROR_ATTEMPT_EDIT_SP = 'Can\'t let you do that yet! Please onboard Suggested People first!';
export const ERROR_AUTHENTICATION = 'An error occurred during authentication. Please login again!';
+export const ERROR_BADGES_EXCEED_LIMIT = 'You can\'t have more than 5 badges!';
export const ERROR_CATEGORY_CREATION = 'There was a problem creating your categories. Please refresh and try again.';
export const ERROR_CATEGORY_UPDATE = 'There was a problem updating your categories. Please refresh and try again';
export const ERROR_DELETE_CATEGORY = 'There was a problem while deleting category. Please try again';
@@ -35,6 +36,7 @@ export const ERROR_REGISTRATION = (str: string) => `Registration failed πŸ˜”, ${
export const ERROR_SELECT_BIRTHDAY = 'Please select your birthday';
export const ERROR_SELECT_CLASS_YEAR = 'Please select your Class Year';
export const ERROR_SELECT_GENDER = 'Please select your gender';
+export const ERROR_SELECT_UNIVERSITY = 'Please select your University';
export const ERROR_SERVER_DOWN = 'mhm, looks like our servers are down, please refresh and try again in a few mins';
export const ERROR_SOMETHING_WENT_WRONG = 'Oh dear, don’t worry someone will be held responsible for this error, In the meantime refresh the app';
export const ERROR_SOMETHING_WENT_WRONG_REFRESH = "Ha, looks like this one's on us, please refresh and try again";
@@ -44,21 +46,20 @@ export const ERROR_UNABLE_TO_FIND_PROFILE = 'We were unable to find this profile
export const ERROR_UNABLE_TO_VIEW_PROFILE = 'Unable to view this profile';
export const ERROR_UPLOAD = 'An error occurred while uploading. Please try again!';
export const ERROR_UPLOAD_BADGES = 'Unable to upload your badges. Please retry!';
-export const ERROR_BADGES_EXCEED_LIMIT = 'You can\'t have more than 5 badges!';
export const ERROR_UPLOAD_LARGE_PROFILE_PIC = "Can't have the first image seen on the profile be blank, please upload a large picture";
export const ERROR_UPLOAD_MOMENT = 'Unable to upload moment. Please retry';
-export const ERROR_UPLOAD_SP_PHOTO = 'Unable to update suggested people photo. Please retry!';
export const ERROR_UPLOAD_SMALL_PROFILE_PIC = "Can't have a profile without a pic to represent you, please upload a small profile picture";
+export const ERROR_UPLOAD_SP_PHOTO = 'Unable to update suggested people photo. Please retry!';
export const ERROR_VERIFICATION_FAILED_SHORT = 'Verification failed πŸ˜“';
export const MARKED_AS_MSG = (str: string) => `Marked as ${str}`;
export const MOMENT_DELETED_MSG = 'Moment deleted....Some moments have to go, to create space for greater ones';
export const NO_NEW_NOTIFICATIONS = 'You have no new notifications';
export const NO_RESULTS_FOUND = 'No Results Found!';
+export const SUCCESS_BADGES_UPDATE = 'Badges updated successfully!'
export const SUCCESS_CATEGORY_DELETE = 'Category successfully deleted, but its memory will live on';
export const SUCCESS_INVITATION_CODE = 'Welcome to Tagg!';
export const SUCCESS_LINK = (str: string) => `Successfully linked ${str} πŸŽ‰`;
export const SUCCESS_PIC_UPLOAD = 'Beautiful, the picture was uploaded successfully!';
-export const SUCCESS_BADGES_UPDATE = 'Badges updated successfully!'
export const SUCCESS_PWD_RESET = 'Your password was reset successfully!';
export const SUCCESS_VERIFICATION_CODE_SENT = 'New verification code sent! Check your phone messages for your code';
export const UP_TO_DATE = 'Up-to-Date!';
diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx
index 6d9d3a97..97f4fe87 100644
--- a/src/screens/onboarding/Login.tsx
+++ b/src/screens/onboarding/Login.tsx
@@ -27,7 +27,7 @@ import {
import {OnboardingStackParams} from '../../routes/onboarding';
import {fcmService} from '../../services';
import {RootState} from '../../store/rootReducer';
-import {BackgroundGradientType} from '../../types';
+import {BackgroundGradientType, UniversityType} from '../../types';
import {normalize, userLogin} from '../../utils';
import UpdateRequired from './UpdateRequired';
@@ -156,18 +156,39 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
let statusCode = response.status;
let data = await response.json();
+ if (statusCode === 200) {
+ await AsyncStorage.setItem('token', data.token);
+ await AsyncStorage.setItem('userId', data.UserID);
+ await AsyncStorage.setItem('username', username);
+ }
+
if (statusCode === 200 && data.isOnboarded) {
//Stores token received in the response into client's AsynStorage
try {
- await AsyncStorage.setItem('token', data.token);
- await AsyncStorage.setItem('userId', data.UserID);
- await AsyncStorage.setItem('username', username);
userLogin(dispatch, {userId: data.UserID, username});
fcmService.sendFcmTokenToServer();
} catch (err) {
Alert.alert(ERROR_INVALID_LOGIN);
}
+ } else if (
+ statusCode === 200 &&
+ data.university === UniversityType.Empty
+ ) {
+ /**
+ * A user account was created during onboarding step 2 but user didn't
+ * finish step 3, thus does not have a universtiy.
+ * Redirecting user back to onboarding to finish the process
+ */
+ navigation.navigate('OnboardingStepThree', {
+ userId: data.UserID,
+ username: username,
+ });
} else if (statusCode === 200 && !data.isOnboarded) {
+ /**
+ * A user account was created and finished the onboarding process but
+ * did not have an invitation code at the time so the user's account
+ * is not activated (isOnboarded) yet.
+ */
navigation.navigate('InvitationCodeVerification', {
userId: data.UserID,
username: username,
diff --git a/src/screens/onboarding/OnboardingStepThree.tsx b/src/screens/onboarding/OnboardingStepThree.tsx
index f22d720f..29028421 100644
--- a/src/screens/onboarding/OnboardingStepThree.tsx
+++ b/src/screens/onboarding/OnboardingStepThree.tsx
@@ -1,4 +1,3 @@
-import AsyncStorage from '@react-native-community/async-storage';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import moment from 'moment';
@@ -20,24 +19,19 @@ import {
RegistrationWizard,
TaggDropDown,
TaggInput,
+ UniversitySelection,
} from '../../components';
+import {CLASS_YEAR_LIST, genderRegex, TAGG_PURPLE} from '../../constants';
import {
- CLASS_YEAR_LIST,
- EDIT_PROFILE_ENDPOINT,
- genderRegex,
- TAGG_PURPLE,
-} from '../../constants';
-import {
- ERROR_DOUBLE_CHECK_CONNECTION,
- ERROR_PROFILE_CREATION_SHORT,
ERROR_SELECT_BIRTHDAY,
ERROR_SELECT_CLASS_YEAR,
ERROR_SELECT_GENDER,
- ERROR_SOMETHING_WENT_WRONG_REFRESH,
+ ERROR_SELECT_UNIVERSITY,
ERROR_UPLOAD_SMALL_PROFILE_PIC,
} from '../../constants/strings';
import {OnboardingStackParams} from '../../routes/onboarding';
-import {BackgroundGradientType} from '../../types';
+import {patchEditProfile} from '../../services';
+import {BackgroundGradientType, UniversityType} from '../../types';
import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
type OnboardingStepThreeRouteProp = RouteProp<
@@ -53,15 +47,25 @@ interface OnboardingStepThreeProps {
navigation: OnboardingStepThreeNavigationProp;
}
+type FormType = {
+ smallPic: string;
+ university: UniversityType;
+ birthdate: string | undefined;
+ gender: string;
+ isValidGender: boolean;
+ classYear: number;
+ attemptedSubmit: boolean;
+};
+
const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
route,
navigation,
}) => {
const {userId, username} = route.params;
- let emptyDate: string | undefined;
- const [form, setForm] = React.useState({
+ const [form, setForm] = React.useState<FormType>({
smallPic: '',
- birthdate: emptyDate,
+ university: UniversityType.Empty,
+ birthdate: undefined,
gender: '',
isValidGender: true,
classYear: -1,
@@ -164,7 +168,11 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
Alert.alert(ERROR_SELECT_CLASS_YEAR);
return;
}
- if (form.birthdate === emptyDate) {
+ if (form.university === UniversityType.Empty) {
+ Alert.alert(ERROR_SELECT_UNIVERSITY);
+ return;
+ }
+ if (!form.birthdate) {
Alert.alert(ERROR_SELECT_BIRTHDAY);
return;
}
@@ -178,7 +186,6 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
attemptedSubmit: true,
});
}
- let invalidFields: boolean = false;
const request = new FormData();
if (form.smallPic) {
request.append('smallProfilePicture', {
@@ -188,16 +195,13 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
});
}
- if (form.birthdate) {
- request.append('birthday', form.birthdate);
- }
if (customGender) {
if (form.isValidGender) {
request.append('gender', form.gender);
} else {
setForm({...form, attemptedSubmit: false});
setTimeout(() => setForm({...form, attemptedSubmit: true}));
- invalidFields = true;
+ return;
}
} else {
if (form.isValidGender) {
@@ -205,47 +209,20 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
}
}
- if (form.classYear !== -1) {
- request.append('university_class', form.classYear);
- }
-
- if (invalidFields) {
- return;
- }
+ request.append('birthday', form.birthdate);
+ request.append('university_class', form.classYear);
+ request.append('university', form.university);
- const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`;
- try {
- const token = await AsyncStorage.getItem('token');
- let response = await fetch(endpoint, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'multipart/form-data',
- Authorization: 'Token ' + token,
- },
- body: request,
- });
- let statusCode = response.status;
- let data = await response.json();
- if (statusCode === 200) {
+ patchEditProfile(request, userId)
+ .then((_) =>
navigation.navigate('InvitationCodeVerification', {
userId: route.params.userId,
username: username,
- });
- } else if (statusCode === 400) {
- Alert.alert(
- 'Profile update failed. πŸ˜”',
- data.error || 'Something went wrong! 😭',
- );
- } else {
- Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH);
- }
- } catch (error) {
- Alert.alert(ERROR_PROFILE_CREATION_SHORT, ERROR_DOUBLE_CHECK_CONNECTION);
- return {
- name: 'Profile creation error',
- description: error,
- };
- }
+ }),
+ )
+ .catch((error) => {
+ Alert.alert(error);
+ });
};
return (
@@ -264,6 +241,15 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({
/>
</View>
<View style={styles.contentContainer}>
+ <UniversitySelection
+ selected={form.university}
+ setSelected={(selected) => {
+ setForm({
+ ...form,
+ university: selected,
+ });
+ }}
+ />
<TaggDropDown
onValueChange={(value: string) => handleClassYearUpdate(value)}
items={classYearList}
diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx
index 56bed11f..8afaeb6d 100644
--- a/src/screens/profile/EditProfile.tsx
+++ b/src/screens/profile/EditProfile.tsx
@@ -46,6 +46,7 @@ import {
ERROR_UPLOAD_SMALL_PROFILE_PIC,
} from '../../constants/strings';
import TaggLoadingIndicator from '../../components/common/TaggLoadingIndicator';
+import {patchEditProfile} from '../../services';
type EditProfileNavigationProp = StackNavigationProp<
MainStackParams,
@@ -364,38 +365,15 @@ const EditProfile: React.FC<EditProfileProps> = ({route, navigation}) => {
return;
}
- const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`;
- try {
- const token = await AsyncStorage.getItem('token');
- let response = await fetch(endpoint, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'multipart/form-data',
- Authorization: 'Token ' + token,
- },
- body: request,
- });
- let statusCode = response.status;
- let data = await response.json();
- if (statusCode === 200) {
+ patchEditProfile(request, userId)
+ .then((_) => {
setNeedsUpdate(true);
navigation.pop();
- } else if (statusCode === 400) {
- Alert.alert(
- 'Profile update failed. πŸ˜”',
- data.error || 'Something went wrong! 😭',
- );
- } else {
- Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH);
- }
- } catch (error) {
- Alert.alert(ERROR_DOUBLE_CHECK_CONNECTION);
- return {
- name: 'Profile creation error',
- description: error,
- };
- }
- }, [isCustomGender, form, navigation, userId]);
+ })
+ .catch((error) => {
+ Alert.alert(error);
+ });
+ }, [form, isCustomGender, university_class, userId, navigation]);
React.useLayoutEffect(() => {
navigation.setOptions({
diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts
index e00c0530..085787c3 100644
--- a/src/services/UserProfileService.ts
+++ b/src/services/UserProfileService.ts
@@ -2,6 +2,7 @@ import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import {Alert} from 'react-native';
import {
+ EDIT_PROFILE_ENDPOINT,
GET_FB_POSTS_ENDPOINT,
GET_IG_POSTS_ENDPOINT,
GET_TWITTER_POSTS_ENDPOINT,
@@ -296,16 +297,19 @@ export const sendRegister = async (
password: string,
) => {
try {
+ const form = new FormData();
+ form.append('first_name', firstName);
+ form.append('last_name', lastName);
+ form.append('email', email);
+ form.append('phone_number', phone);
+ form.append('username', username);
+ form.append('password', password);
const response = await fetch(REGISTER_ENDPOINT, {
method: 'POST',
- body: JSON.stringify({
- first_name: firstName,
- last_name: lastName,
- email: email,
- phone_number: phone,
- username: username,
- password: password,
- }),
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ body: form,
});
return response;
} catch (error) {
@@ -334,3 +338,31 @@ export const fetchUserProfile = async (userId: string, token?: string) => {
return undefined;
}
};
+
+export const patchEditProfile = async (form: FormData, userId: string) => {
+ const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`;
+ try {
+ const token = await AsyncStorage.getItem('token');
+ let response = await fetch(endpoint, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: 'Token ' + token,
+ },
+ body: form,
+ });
+ let statusCode = response.status;
+ if (statusCode === 200) {
+ return true;
+ } else if (statusCode === 400) {
+ let data = await response.json();
+ throw (
+ 'Profile update failed. πŸ˜”' + data.error || 'Something went wrong! 😭'
+ );
+ } else {
+ throw ERROR_SOMETHING_WENT_WRONG_REFRESH;
+ }
+ } catch (error) {
+ throw ERROR_DOUBLE_CHECK_CONNECTION;
+ }
+};
diff --git a/src/types/types.ts b/src/types/types.ts
index 9a40e4c0..48b52366 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -21,13 +21,20 @@ export interface CategoryPreviewType {
export type FriendshipStatusType = 'friends' | 'requested' | 'no_record';
-export type ProfileType = {
+export enum UniversityType {
+ Brown = 'Brown University',
+ Cornell = 'Cornell University',
+ // Harvard = 'Harvard University',
+ Empty = '',
+}
+
+export interface ProfileType {
profile_pic: string;
header_pic: string;
profile_info: ProfileInfoType;
moment_categories: string[];
linked_socials: string[];
-};
+}
export interface ProfileInfoType {
name: string;
@@ -35,6 +42,7 @@ export interface ProfileInfoType {
website: string;
gender: string;
university_class: number;
+ university: UniversityType;
profile_completion_stage: number;
suggested_people_linked: number;
birthday: Date | undefined;