aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAshm Walia <40498934+ashmgarv@users.noreply.github.com>2020-10-18 16:37:32 -0700
committerGitHub <noreply@github.com>2020-10-18 19:37:32 -0400
commitab7fa09af967e0a8cf2ca53dfb24f8bc8a6886f7 (patch)
tree898e7aa42529eda91964ac1a18aa1881689554f2
parent79d237f616c37940f5d476eb1dca6b5d05cf148a (diff)
[TMA 279] Ability to search and view someone's profile (#58)
* Batch one : major changes * WIP checkpoint * The one before the final touch * Probable final touch * ran yarn lint D: * linter broke something * fixed a small bug * Addressed a small nitpick * Well abstracted now Co-authored-by: Ivan Chen <ivan@thetaggid.com>
-rw-r--r--src/App.tsx10
-rw-r--r--src/components/common/AvatarTitle.tsx12
-rw-r--r--src/components/profile/Avatar.tsx9
-rw-r--r--src/components/profile/Content.tsx24
-rw-r--r--src/components/profile/Cover.tsx9
-rw-r--r--src/components/profile/ProfileBody.tsx9
-rw-r--r--src/components/profile/ProfileHeader.tsx14
-rw-r--r--src/components/search/SearchResult.tsx21
-rw-r--r--src/components/taggs/Tagg.tsx10
-rw-r--r--src/components/taggs/TaggsBar.tsx11
-rw-r--r--src/routes/authentication/AuthProvider.tsx115
-rw-r--r--src/routes/index.ts2
-rw-r--r--src/routes/profile/Profile.tsx40
-rw-r--r--src/routes/profile/ProfileStack.tsx6
-rw-r--r--src/routes/tabs/NavigationBar.tsx14
-rw-r--r--src/routes/viewProfile/ProfileProvider.tsx91
-rw-r--r--src/routes/viewProfile/index.ts2
-rw-r--r--src/screens/onboarding/InvitationCodeVerification.tsx27
-rw-r--r--src/screens/profile/CaptionScreen.tsx10
-rw-r--r--src/screens/profile/ProfileScreen.tsx20
-rw-r--r--src/screens/profile/SocialMediaTaggs.tsx12
-rw-r--r--src/services/UserProfileService.ts121
-rw-r--r--src/services/index.ts1
23 files changed, 410 insertions, 180 deletions
diff --git a/src/App.tsx b/src/App.tsx
index 2e6865fd..6d51da34 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,13 +1,15 @@
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
-import Routes, {AuthProvider} from './routes';
+import Routes, {AuthProvider, ProfileProvider} from './routes';
const App = () => {
return (
<AuthProvider>
- <NavigationContainer>
- <Routes />
- </NavigationContainer>
+ <ProfileProvider>
+ <NavigationContainer>
+ <Routes />
+ </NavigationContainer>
+ </ProfileProvider>
</AuthProvider>
);
};
diff --git a/src/components/common/AvatarTitle.tsx b/src/components/common/AvatarTitle.tsx
index 8c82dca9..e9998113 100644
--- a/src/components/common/AvatarTitle.tsx
+++ b/src/components/common/AvatarTitle.tsx
@@ -2,13 +2,19 @@ import React from 'react';
import {Image, StyleSheet} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {AVATAR_DIM, AVATAR_GRADIENT_DIM, TAGGS_GRADIENT} from '../../constants';
-import {AuthContext} from '../../routes/authentication';
+import {AuthContext, ProfileContext} from '../../routes/';
/**
* An image component that returns the <Image> of the icon for a specific social media platform.
*/
-const AvatarTitle: React.FC = () => {
- const {avatar} = React.useContext(AuthContext);
+
+type AvatarTitleProps = {
+ isProfileView: boolean;
+};
+const AvatarTitle: React.FC<AvatarTitleProps> = ({isProfileView}) => {
+ const {avatar} = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
return (
<LinearGradient
colors={[TAGGS_GRADIENT.start, TAGGS_GRADIENT.end]}
diff --git a/src/components/profile/Avatar.tsx b/src/components/profile/Avatar.tsx
index a0f7596c..aca3bf4d 100644
--- a/src/components/profile/Avatar.tsx
+++ b/src/components/profile/Avatar.tsx
@@ -1,13 +1,16 @@
import React from 'react';
import {Image, StyleSheet} from 'react-native';
-import {AuthContext} from '../../routes/authentication';
+import {AuthContext, ProfileContext} from '../../routes/';
const PROFILE_DIM = 100;
interface AvatarProps {
style: object;
+ isProfileView: boolean;
}
-const Avatar: React.FC<AvatarProps> = ({style}) => {
- const {avatar} = React.useContext(AuthContext);
+const Avatar: React.FC<AvatarProps> = ({style, isProfileView}) => {
+ const {avatar} = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
return (
<Image
style={[styles.image, style]}
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index a3b9e74a..8d368747 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,5 +1,6 @@
import React, {useState} from 'react';
import {LayoutChangeEvent, StyleSheet, View} from 'react-native';
+import {Text} from 'react-native-animatable';
import Animated from 'react-native-reanimated';
import {defaultMoments} from '../../constants';
import {SCREEN_HEIGHT} from '../../utils';
@@ -11,8 +12,9 @@ import ProfileHeader from './ProfileHeader';
interface ContentProps {
y: Animated.Value<number>;
+ isProfileView: boolean;
}
-const Content: React.FC<ContentProps> = ({y}) => {
+const Content: React.FC<ContentProps> = ({y, isProfileView}) => {
const [profileBodyHeight, setProfileBodyHeight] = useState(0);
const onLayout = (e: LayoutChangeEvent) => {
const {height} = e.nativeEvent.layout;
@@ -26,15 +28,19 @@ const Content: React.FC<ContentProps> = ({y}) => {
scrollEventThrottle={1}
stickyHeaderIndices={[2, 4]}>
<ProfileCutout>
- <ProfileHeader />
+ <ProfileHeader {...{isProfileView}} />
</ProfileCutout>
- <ProfileBody {...{onLayout}} />
- <TaggsBar {...{y, profileBodyHeight}} />
- <View style={styles.momentsContainer}>
- {defaultMoments.map((title, index) => (
- <Moment key={index} title={title} images={[]} />
- ))}
- </View>
+ <ProfileBody {...{onLayout, isProfileView}} />
+ <TaggsBar {...{y, profileBodyHeight, isProfileView}} />
+ {!isProfileView ? (
+ <View style={styles.momentsContainer}>
+ {defaultMoments.map((title, index) => (
+ <Moment key={index} title={title} images={[]} />
+ ))}
+ </View>
+ ) : (
+ <React.Fragment />
+ )}
</Animated.ScrollView>
);
};
diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx
index 01199f06..37ecb9bd 100644
--- a/src/components/profile/Cover.tsx
+++ b/src/components/profile/Cover.tsx
@@ -2,14 +2,17 @@ import React from 'react';
import {Image, StyleSheet} from 'react-native';
import Animated from 'react-native-reanimated';
import {IMAGE_WIDTH, COVER_HEIGHT} from '../../constants';
-import {AuthContext} from '../../routes/authentication';
+import {AuthContext, ProfileContext} from '../../routes/';
const {interpolate, Extrapolate} = Animated;
interface CoverProps {
y: Animated.Value<number>;
+ isProfileView: boolean;
}
-const Cover: React.FC<CoverProps> = ({y}) => {
- const {cover} = React.useContext(AuthContext);
+const Cover: React.FC<CoverProps> = ({y, isProfileView}) => {
+ const {cover} = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
const scale: Animated.Node<number> = interpolate(y, {
inputRange: [-COVER_HEIGHT, 0],
outputRange: [1.5, 1.25],
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index e8d8de62..53b86708 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -1,15 +1,18 @@
import React from 'react';
import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native';
-import {AuthContext} from '../../routes/authentication';
+import {AuthContext, ProfileContext} from '../../routes/';
interface ProfileBodyProps {
onLayout: (event: LayoutChangeEvent) => void;
+ isProfileView: boolean;
}
-const ProfileBody: React.FC<ProfileBodyProps> = ({onLayout}) => {
+const ProfileBody: React.FC<ProfileBodyProps> = ({onLayout, isProfileView}) => {
const {
profile,
user: {username},
- } = React.useContext(AuthContext);
+ } = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
const {biography, website} = profile;
return (
<View onLayout={onLayout} style={styles.container}>
diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx
index ec382357..62c103fd 100644
--- a/src/components/profile/ProfileHeader.tsx
+++ b/src/components/profile/ProfileHeader.tsx
@@ -4,16 +4,22 @@ import Avatar from './Avatar';
import FollowCount from './FollowCount';
import {View, Text, StyleSheet} from 'react-native';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
-import {AuthContext} from '../../routes/authentication';
+import {AuthContext, ProfileContext} from '../../routes/';
-const ProfileHeader: React.FC = () => {
+type ProfileHeaderProps = {
+ isProfileView: boolean;
+};
+
+const ProfileHeader: React.FC<ProfileHeaderProps> = ({isProfileView}) => {
const {
profile: {name},
- } = React.useContext(AuthContext);
+ } = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
return (
<View style={styles.container}>
<View style={styles.row}>
- <Avatar style={styles.avatar} />
+ <Avatar style={styles.avatar} isProfileView={isProfileView} />
<View style={styles.header}>
<Text style={styles.name}>{name}</Text>
<View style={styles.row}>
diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx
index 952f08f7..cc960308 100644
--- a/src/components/search/SearchResult.tsx
+++ b/src/components/search/SearchResult.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect, useState} from 'react';
+import React, {useEffect, useState, useContext} from 'react';
import {ProfilePreviewType} from '../../types';
import {
View,
@@ -8,10 +8,12 @@ import {
ViewProps,
TouchableOpacity,
} from 'react-native';
+import {useNavigation} from '@react-navigation/native';
import RNFetchBlob from 'rn-fetch-blob';
import AsyncStorage from '@react-native-community/async-storage';
import {AVATAR_PHOTO_ENDPOINT} from '../../constants';
import {UserType} from '../../types';
+import {ProfileContext} from '../../routes/viewProfile';
const NO_USER: UserType = {
userId: '',
username: '',
@@ -24,6 +26,8 @@ const SearchResult: React.FC<SearchResultProps> = ({
profilePreview: {username, first_name, last_name, id},
style,
}) => {
+ const navigation = useNavigation();
+ const {loadProfile} = useContext(ProfileContext);
const [avatarURI, setAvatarURI] = useState<string | null>(null);
const [user, setUser] = useState<UserType>(NO_USER);
useEffect(() => {
@@ -38,7 +42,7 @@ const SearchResult: React.FC<SearchResultProps> = ({
const response = await RNFetchBlob.config({
fileCache: true,
appendExt: 'jpg',
- }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}`, {
+ }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}/`, {
Authorization: 'Token ' + token,
});
const status = response.info().status;
@@ -66,7 +70,7 @@ const SearchResult: React.FC<SearchResultProps> = ({
* Cache maintains 10 recently searched users, popping off the oldest one if
* needed to make space.
*/
- const addToRecentlyStored = async () => {
+ const addToRecentlyStoredAndNavigateToProfile = async () => {
let user: ProfilePreviewType = {
id,
username,
@@ -95,6 +99,15 @@ const SearchResult: React.FC<SearchResultProps> = ({
} else {
recentlySearchedList = [user];
}
+
+ //Load user profile and navigate to ProfileView
+ //Load user profile makes sure that we actually load profile of the user the logged in user want to view
+ //Not sure if we should make this call before caching the search results ??
+ loadProfile(user.id, user.username);
+ navigation.navigate('Profile', {
+ isProfileView: true,
+ });
+
try {
let recentlySearchedListString = JSON.stringify(recentlySearchedList);
await AsyncStorage.setItem(
@@ -111,7 +124,7 @@ const SearchResult: React.FC<SearchResultProps> = ({
return (
<TouchableOpacity
- onPress={addToRecentlyStored}
+ onPress={addToRecentlyStoredAndNavigateToProfile}
style={[styles.container, style]}>
<Image
style={styles.avatar}
diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx
index 341a713a..d6cffee5 100644
--- a/src/components/taggs/Tagg.tsx
+++ b/src/components/taggs/Tagg.tsx
@@ -1,12 +1,15 @@
import {useNavigation} from '@react-navigation/native';
import React from 'react';
-import {StyleSheet, TouchableOpacity, View, ViewProps} from 'react-native';
+import {StyleSheet, TouchableOpacity, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {TAGGS_GRADIENT} from '../../constants';
-interface TaggProps extends ViewProps {}
+interface TaggProps {
+ style: object;
+ isProfileView: boolean;
+}
-const Tagg: React.FC<TaggProps> = ({style}) => {
+const Tagg: React.FC<TaggProps> = ({style, isProfileView}) => {
const navigation = useNavigation();
return (
@@ -14,6 +17,7 @@ const Tagg: React.FC<TaggProps> = ({style}) => {
onPress={() =>
navigation.navigate('SocialMediaTaggs', {
socialMediaType: 'Instagram',
+ isProfileView: isProfileView,
})
}>
<LinearGradient
diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx
index 1022c4fc..933f355d 100644
--- a/src/components/taggs/TaggsBar.tsx
+++ b/src/components/taggs/TaggsBar.tsx
@@ -10,11 +10,18 @@ const {View, ScrollView, interpolate, Extrapolate} = Animated;
interface TaggsBarProps {
y: Animated.Value<number>;
profileBodyHeight: number;
+ isProfileView: boolean;
}
-const TaggsBar: React.FC<TaggsBarProps> = ({y, profileBodyHeight}) => {
+const TaggsBar: React.FC<TaggsBarProps> = ({
+ y,
+ profileBodyHeight,
+ isProfileView,
+}) => {
const taggs: Array<JSX.Element> = [];
for (let i = 0; i < 10; i++) {
- taggs.push(<Tagg key={i} style={styles.tagg} />);
+ taggs.push(
+ <Tagg key={i} style={styles.tagg} isProfileView={isProfileView} />,
+ );
}
const shadowOpacity: Animated.Node<number> = interpolate(y, {
inputRange: [
diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx
index e5956eb2..6f577a73 100644
--- a/src/routes/authentication/AuthProvider.tsx
+++ b/src/routes/authentication/AuthProvider.tsx
@@ -1,20 +1,19 @@
import React, {useEffect} from 'react';
import {createContext, useState} from 'react';
-import RNFetchBlob from 'rn-fetch-blob';
-import AsyncStorage from '@react-native-community/async-storage';
import {
UserType,
ProfileType,
InstagramPostType,
ProfilePreviewType,
} from '../../types';
+import AsyncStorage from '@react-native-community/async-storage';
import {
- PROFILE_INFO_ENDPOINT,
- AVATAR_PHOTO_ENDPOINT,
- COVER_PHOTO_ENDPOINT,
- GET_IG_POSTS_ENDPOINT,
-} from '../../constants';
-import {Alert} from 'react-native';
+ loadProfileInfo,
+ loadAvatar,
+ loadCover,
+ loadInstaPosts,
+ loadRecentlySearchedUsers,
+} from '../../services';
interface AuthContextProps {
user: UserType;
@@ -63,96 +62,6 @@ const AuthProvider: React.FC = ({children}) => {
if (!userId) {
return;
}
- const loadProfileInfo = async (token: string) => {
- try {
- const response = await fetch(PROFILE_INFO_ENDPOINT + `${userId}/`, {
- method: 'GET',
- headers: {
- Authorization: 'Token ' + token,
- },
- });
- const status = response.status;
- if (status === 200) {
- const info = await response.json();
- let {name, biography, website} = info;
- setProfile({name, biography, website});
- }
- } catch (error) {
- Alert.alert(
- 'Something went wrong! 😭',
- "Would you believe me if I told you that I don't know what happened?",
- );
- }
- };
- const loadAvatar = async (token: string) => {
- try {
- const response = await RNFetchBlob.config({
- fileCache: true,
- appendExt: 'jpg',
- }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${userId}/`, {
- Authorization: 'Token ' + token,
- });
- const status = response.info().status;
- if (status === 200) {
- setAvatar(response.path());
- } else {
- setAvatar('');
- }
- } catch (error) {
- console.log(error);
- }
- };
- const loadCover = async (token: string) => {
- try {
- let response = await RNFetchBlob.config({
- fileCache: true,
- appendExt: 'jpg',
- }).fetch('GET', COVER_PHOTO_ENDPOINT + `${userId}/`, {
- Authorization: 'Token ' + token,
- });
- const status = response.info().status;
- if (status === 200) {
- setCover(response.path());
- } else {
- setCover('');
- }
- } catch (error) {
- console.log(error);
- }
- };
- const loadInstaPosts = async (token: string) => {
- try {
- const response = await fetch(GET_IG_POSTS_ENDPOINT + `${userId}/`, {
- method: 'GET',
- headers: {
- Authorization: 'Token ' + token,
- },
- });
- const status = response.status;
- if (status === 200) {
- let ig_posts = await response.json();
- setInstaPosts(ig_posts);
- } else {
- setInstaPosts([]);
- }
- } 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?",
- );
- }
- };
- const loadRecentlySearchedUsers = async () => {
- try {
- const asyncCache = await AsyncStorage.getItem(
- '@recently_searched_users',
- );
- asyncCache != null ? setRecentSearches(JSON.parse(asyncCache)) : null;
- } catch (e) {
- console.log(e);
- }
- };
const loadData = async () => {
try {
@@ -161,11 +70,11 @@ const AuthProvider: React.FC = ({children}) => {
setUser(NO_USER);
return;
}
- loadProfileInfo(token);
- loadAvatar(token);
- loadCover(token);
- loadInstaPosts(token);
- loadRecentlySearchedUsers();
+ loadProfileInfo(token, userId, setProfile);
+ loadAvatar(token, userId, setAvatar);
+ loadCover(token, userId, setCover);
+ loadInstaPosts(token, userId, setInstaPosts);
+ loadRecentlySearchedUsers(setRecentSearches);
} catch (err) {
console.log(err);
}
diff --git a/src/routes/index.ts b/src/routes/index.ts
index c06845aa..7e8a84ce 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -1,5 +1,7 @@
export {default as AuthProvider} from './authentication';
+export {default as ProfileProvider} from './viewProfile';
export * from './authentication';
+export * from './viewProfile';
export * from './onboarding';
export * from './profile';
export {default} from './Routes';
diff --git a/src/routes/profile/Profile.tsx b/src/routes/profile/Profile.tsx
index eaf5f3aa..b39b726e 100644
--- a/src/routes/profile/Profile.tsx
+++ b/src/routes/profile/Profile.tsx
@@ -1,14 +1,36 @@
import React from 'react';
+import {
+ ProfileScreen,
+ CaptionScreen,
+ SocialMediaTaggs,
+ SearchScreen,
+} from '../../screens';
+import {RouteProp} from '@react-navigation/native';
+import {ProfileStack, ProfileStackParams} from './ProfileStack';
import {AvatarTitle} from '../../components';
-import {ProfileScreen, CaptionScreen, SocialMediaTaggs} from '../../screens';
-import {ProfileStack} from './ProfileStack';
-const Profile: React.FC = () => {
+type ProfileScreenRouteProps = RouteProp<ProfileStackParams, 'Profile'>;
+
+interface ProfileScreenProps {
+ route: ProfileScreenRouteProps;
+}
+
+const Profile: React.FC<ProfileScreenProps> = ({route}) => {
+ const {isProfileView} = route.params;
return (
<ProfileStack.Navigator
- initialRouteName="Profile"
+ initialRouteName={!isProfileView ? `Profile` : `Search`}
screenOptions={{headerShown: false}}>
- <ProfileStack.Screen name="Profile" component={ProfileScreen} />
+ <ProfileStack.Screen
+ name="Profile"
+ component={ProfileScreen}
+ initialParams={{isProfileView: isProfileView}}
+ />
+ {isProfileView ? (
+ <ProfileStack.Screen name="Search" component={SearchScreen} />
+ ) : (
+ <React.Fragment />
+ )}
<ProfileStack.Screen
name="SocialMediaTaggs"
component={SocialMediaTaggs}
@@ -17,10 +39,14 @@ const Profile: React.FC = () => {
headerTransparent: true,
headerBackTitleVisible: false,
headerTintColor: 'white',
- headerTitle: () => <AvatarTitle />,
+ headerTitle: () => <AvatarTitle isProfileView={isProfileView} />,
}}
/>
- <ProfileStack.Screen name="CaptionScreen" component={CaptionScreen} />
+ {!isProfileView ? (
+ <ProfileStack.Screen name="CaptionScreen" component={CaptionScreen} />
+ ) : (
+ <React.Fragment />
+ )}
</ProfileStack.Navigator>
);
};
diff --git a/src/routes/profile/ProfileStack.tsx b/src/routes/profile/ProfileStack.tsx
index c1da67c1..63ab9a10 100644
--- a/src/routes/profile/ProfileStack.tsx
+++ b/src/routes/profile/ProfileStack.tsx
@@ -1,10 +1,14 @@
import {createStackNavigator} from '@react-navigation/stack';
export type ProfileStackParams = {
- Profile: undefined;
+ Search: undefined;
+ Profile: {
+ isProfileView: boolean;
+ };
SocialMediaTaggs: {
socialMediaType: string;
socialMediaHandle: string;
+ isProfileView: boolean;
};
CaptionScreen: {title: string; image: object};
};
diff --git a/src/routes/tabs/NavigationBar.tsx b/src/routes/tabs/NavigationBar.tsx
index 456e923f..2852b565 100644
--- a/src/routes/tabs/NavigationBar.tsx
+++ b/src/routes/tabs/NavigationBar.tsx
@@ -1,7 +1,7 @@
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import React from 'react';
import {NavigationIcon} from '../../components';
-import {Home, Notifications, SearchScreen, Upload} from '../../screens';
+import {Home, Notifications, Upload} from '../../screens';
import Profile from '../profile';
const Tabs = createBottomTabNavigator();
@@ -57,10 +57,18 @@ const NavigationBar: React.FC = () => {
},
}}>
<Tabs.Screen name="Home" component={Home} />
- <Tabs.Screen name="Search" component={SearchScreen} />
+ <Tabs.Screen
+ name="Search"
+ component={Profile}
+ initialParams={{isProfileView: true}}
+ />
<Tabs.Screen name="Upload" component={Upload} />
<Tabs.Screen name="Notifications" component={Notifications} />
- <Tabs.Screen name="Profile" component={Profile} />
+ <Tabs.Screen
+ name="Profile"
+ component={Profile}
+ initialParams={{isProfileView: false}}
+ />
</Tabs.Navigator>
);
};
diff --git a/src/routes/viewProfile/ProfileProvider.tsx b/src/routes/viewProfile/ProfileProvider.tsx
new file mode 100644
index 00000000..1af7917d
--- /dev/null
+++ b/src/routes/viewProfile/ProfileProvider.tsx
@@ -0,0 +1,91 @@
+import React, {useEffect} from 'react';
+import {createContext, useState} from 'react';
+import AsyncStorage from '@react-native-community/async-storage';
+import {UserType, ProfileType, InstagramPostType} from '../../types';
+
+import {
+ loadProfileInfo,
+ loadAvatar,
+ loadCover,
+ loadInstaPosts,
+ loadRecentlySearchedUsers,
+} from '../../services';
+
+interface ProfileContextProps {
+ user: UserType;
+ profile: ProfileType;
+ loadProfile: (userId: string, username: string) => void;
+ avatar: string | null;
+ cover: string | null;
+ instaPosts: Array<InstagramPostType>;
+}
+const NO_USER: UserType = {
+ userId: '',
+ username: '',
+};
+const NO_PROFILE: ProfileType = {
+ biography: '',
+ website: '',
+ name: '',
+};
+export const ProfileContext = createContext<ProfileContextProps>({
+ user: NO_USER,
+ profile: NO_PROFILE,
+ loadProfile: () => {},
+ avatar: null,
+ cover: null,
+ instaPosts: [],
+});
+
+/**
+ * This is the context provider for user profiles that the logged in user wants to see
+ */
+const ProfileProvider: React.FC = ({children}) => {
+ const [user, setUser] = useState<UserType>(NO_USER);
+ const [profile, setProfile] = useState<ProfileType>(NO_PROFILE);
+ const [avatar, setAvatar] = useState<string | null>(null);
+ const [cover, setCover] = useState<string | null>(null);
+ const [instaPosts, setInstaPosts] = useState<Array<InstagramPostType>>([]);
+
+ const {userId} = user;
+ useEffect(() => {
+ if (!userId) {
+ return;
+ }
+
+ const loadData = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ setUser(NO_USER);
+ return;
+ }
+ loadProfileInfo(token, userId, setProfile);
+ loadAvatar(token, userId, setAvatar);
+ loadCover(token, userId, setCover);
+ loadInstaPosts(token, userId, setInstaPosts);
+ } catch (err) {
+ console.log(err);
+ }
+ };
+ loadData();
+ }, [userId]);
+
+ return (
+ <ProfileContext.Provider
+ value={{
+ user,
+ profile,
+ avatar,
+ cover,
+ instaPosts,
+ loadProfile: (id, username) => {
+ setUser({...user, userId: id, username});
+ },
+ }}>
+ {children}
+ </ProfileContext.Provider>
+ );
+};
+
+export default ProfileProvider;
diff --git a/src/routes/viewProfile/index.ts b/src/routes/viewProfile/index.ts
new file mode 100644
index 00000000..7035ce4a
--- /dev/null
+++ b/src/routes/viewProfile/index.ts
@@ -0,0 +1,2 @@
+export * from './ProfileProvider';
+export {default} from './ProfileProvider';
diff --git a/src/screens/onboarding/InvitationCodeVerification.tsx b/src/screens/onboarding/InvitationCodeVerification.tsx
index fd5f828b..8a3d5982 100644
--- a/src/screens/onboarding/InvitationCodeVerification.tsx
+++ b/src/screens/onboarding/InvitationCodeVerification.tsx
@@ -10,7 +10,7 @@ import {
LoadingIndicator,
} from '../../components';
-import {VERIFY_INVITATION_CODE_ENDPOUNT} from "../../constants"
+import {VERIFY_INVITATION_CODE_ENDPOUNT} from '../../constants';
import {Text} from 'react-native-animatable';
import {
@@ -53,18 +53,19 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({
});
const handleInvitationCodeVerification = async () => {
- if(value.length === 6){
- try {
- let verifyInviteCodeResponse = await fetch(VERIFY_INVITATION_CODE_ENDPOUNT + value + '/', {
- method: 'DELETE',
- });
+ if (value.length === 6) {
+ try {
+ let verifyInviteCodeResponse = await fetch(
+ VERIFY_INVITATION_CODE_ENDPOUNT + value + '/',
+ {
+ method: 'DELETE',
+ },
+ );
if (verifyInviteCodeResponse.status == 200) {
navigation.navigate('RegistrationOne');
} else {
- Alert.alert(
- 'Invalid invitation code 🤔',
- );
+ Alert.alert('Invalid invitation code 🤔');
}
} catch (error) {
Alert.alert(
@@ -76,9 +77,8 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({
description: error,
};
}
- }
- else{
- Alert.alert("The code entered is not valid!");
+ } else {
+ Alert.alert('The code entered is not valid!');
}
};
@@ -97,7 +97,8 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({
<KeyboardAvoidingView behavior="padding" style={styles.form}>
<Text style={styles.formHeader}>Enter the code</Text>
<Text style={styles.description}>
- Please enter the invitation code provided to you by us / your friend. (Use all caps.)
+ Please enter the invitation code provided to you by us / your friend.
+ (Use all caps.)
</Text>
<CodeField
ref={ref}
diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx
index e3040509..53c47a6d 100644
--- a/src/screens/profile/CaptionScreen.tsx
+++ b/src/screens/profile/CaptionScreen.tsx
@@ -42,14 +42,14 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => {
setCaption(caption);
};
- const checkImageUploadStatus = (statusMap : object) => {
- for(let [key, value] of Object.entries(statusMap)){
- if (value != "Success"){
+ const checkImageUploadStatus = (statusMap: object) => {
+ for (let [key, value] of Object.entries(statusMap)) {
+ if (value != 'Success') {
return false;
}
}
return true;
- }
+ };
const handleShare = async () => {
try {
@@ -68,7 +68,7 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => {
});
request.append('moment', title);
request.append('user_id', userId);
- request.append('captions', JSON.stringify({'image':caption}));
+ request.append('captions', JSON.stringify({image: caption}));
let response = await fetch(MOMENTS_UPLOAD_ENDPOINT, {
method: 'POST',
headers: {
diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx
index cc388ffd..7d11fa2a 100644
--- a/src/screens/profile/ProfileScreen.tsx
+++ b/src/screens/profile/ProfileScreen.tsx
@@ -2,20 +2,28 @@ import React from 'react';
import {StatusBar} from 'react-native';
import Animated from 'react-native-reanimated';
import {Content, Cover, TabsGradient} from '../../components';
-import {AuthContext} from '../../routes/authentication';
+import {RouteProp} from '@react-navigation/native';
+import {ProfileStackParams} from '../../routes/profile';
/**
- * Profile Screen for a user's logged in profile
+ * Profile Screen for a user's profile
* including posts, messaging, and settings
*/
-const ProfileScreen: React.FC = () => {
- const {user} = React.useContext(AuthContext);
+
+type ProfileScreenRouteProps = RouteProp<ProfileStackParams, 'Profile'>;
+
+interface ProfileOnboardingProps {
+ route: ProfileScreenRouteProps;
+}
+
+const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => {
+ const {isProfileView} = route.params;
const y = Animated.useValue(0);
return (
<>
<StatusBar />
- <Cover {...{y, user}} />
- <Content {...{y, user}} />
+ <Cover {...{y, isProfileView}} />
+ <Content {...{y, isProfileView}} />
<TabsGradient />
</>
);
diff --git a/src/screens/profile/SocialMediaTaggs.tsx b/src/screens/profile/SocialMediaTaggs.tsx
index 9e4f2aea..ddbebcea 100644
--- a/src/screens/profile/SocialMediaTaggs.tsx
+++ b/src/screens/profile/SocialMediaTaggs.tsx
@@ -4,7 +4,7 @@ import {ScrollView, StatusBar, StyleSheet, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {AVATAR_GRADIENT} from '../../constants';
import {SocialMediaInfo, TabsGradient, TaggsFeed} from '../../components';
-import {AuthContext, ProfileStackParams} from '../../routes';
+import {AuthContext, ProfileStackParams, ProfileContext} from '../../routes';
import {headerBarHeightWithImage, SCREEN_HEIGHT} from '../../utils';
type SocialMediaTaggsRouteProp = RouteProp<
@@ -27,17 +27,21 @@ interface SocialMediaTaggsProps {
* + date posted
* + dark background
*/
-const SocialMediaTaggs: React.FC<SocialMediaTaggsProps> = () => {
+const SocialMediaTaggs: React.FC<SocialMediaTaggsProps> = ({route}) => {
+ const {isProfileView} = route.params;
+ const context = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
const {
user,
profile: {name},
- } = React.useContext(AuthContext);
+ } = context;
// TODO: We should use the passed-in socialmedia type/handle instead.
// Currently don't have an intuitive way of doing so, for now,
// just grabbing from user's AuthContext.
// const {socialMediaType, socialMediaHandle} = route.params;
- const {instaPosts} = React.useContext(AuthContext);
+ const {instaPosts} = context;
const socialMediaType = 'Instagram';
const socialMediaHandle = instaPosts[0].username;
diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts
new file mode 100644
index 00000000..4c3af06a
--- /dev/null
+++ b/src/services/UserProfileService.ts
@@ -0,0 +1,121 @@
+//Abstracted common profile api calls out here
+
+import {Alert} from 'react-native';
+import {
+ PROFILE_INFO_ENDPOINT,
+ AVATAR_PHOTO_ENDPOINT,
+ COVER_PHOTO_ENDPOINT,
+ GET_IG_POSTS_ENDPOINT,
+} from '../constants';
+
+import AsyncStorage from '@react-native-community/async-storage';
+import RNFetchBlob from 'rn-fetch-blob';
+
+export const loadProfileInfo = async (
+ token: string,
+ userId: string,
+ callback: Function,
+) => {
+ try {
+ const response = await fetch(PROFILE_INFO_ENDPOINT + `${userId}/`, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ const status = response.status;
+ if (status === 200) {
+ const info = await response.json();
+ let {name, biography, website} = info;
+ callback({name, biography, website});
+ }
+ } catch (error) {
+ Alert.alert(
+ 'Something went wrong! 😭',
+ "Would you believe me if I told you that I don't know what happened?",
+ );
+ }
+};
+
+export const loadAvatar = async (
+ token: string,
+ userId: string,
+ callback: Function,
+) => {
+ try {
+ const response = await RNFetchBlob.config({
+ fileCache: true,
+ appendExt: 'jpg',
+ }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${userId}/`, {
+ Authorization: 'Token ' + token,
+ });
+ const status = response.info().status;
+ if (status === 200) {
+ callback(response.path());
+ } else {
+ callback('');
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+export const loadCover = async (
+ token: string,
+ userId: string,
+ callback: Function,
+) => {
+ try {
+ let response = await RNFetchBlob.config({
+ fileCache: true,
+ appendExt: 'jpg',
+ }).fetch('GET', COVER_PHOTO_ENDPOINT + `${userId}/`, {
+ Authorization: 'Token ' + token,
+ });
+ const status = response.info().status;
+ if (status === 200) {
+ callback(response.path());
+ } else {
+ callback('');
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+export const loadInstaPosts = async (
+ token: string,
+ userId: string,
+ callback: Function,
+) => {
+ try {
+ const response = await fetch(GET_IG_POSTS_ENDPOINT + `${userId}/`, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ const status = response.status;
+ if (status === 200) {
+ let ig_posts = await response.json();
+ callback(ig_posts);
+ } else {
+ callback([]);
+ }
+ } 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?",
+ );
+ }
+};
+
+export const loadRecentlySearchedUsers = async (callback: Function) => {
+ try {
+ const asyncCache = await AsyncStorage.getItem('@recently_searched_users');
+ asyncCache != null ? callback(JSON.parse(asyncCache)) : null;
+ } catch (e) {
+ console.log(e);
+ }
+};
diff --git a/src/services/index.ts b/src/services/index.ts
new file mode 100644
index 00000000..5cd06cfe
--- /dev/null
+++ b/src/services/index.ts
@@ -0,0 +1 @@
+export * from './UserProfileService';