diff options
-rw-r--r-- | src/components/profile/ProfilePreview.tsx | 7 | ||||
-rw-r--r-- | src/components/search/Explore.tsx | 35 | ||||
-rw-r--r-- | src/components/search/ExploreSection.tsx | 26 | ||||
-rw-r--r-- | src/components/search/ExploreSectionUser.tsx | 81 | ||||
-rw-r--r-- | src/constants/api.ts | 1 | ||||
-rw-r--r-- | src/constants/constants.ts | 11 | ||||
-rw-r--r-- | src/screens/search/SearchScreen.tsx | 51 | ||||
-rw-r--r-- | src/services/ExploreServices.ts | 34 | ||||
-rw-r--r-- | src/store/actions/taggUsers.ts | 10 | ||||
-rw-r--r-- | src/store/initialStates.ts | 15 | ||||
-rw-r--r-- | src/store/reducers/taggUsersReducer.ts | 4 | ||||
-rw-r--r-- | src/types/types.ts | 8 |
12 files changed, 209 insertions, 74 deletions
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index bd015811..6f008540 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -147,7 +147,8 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ screenType, ); } - const userXId = loggedInUser.username === user.username ? undefined : user.id; + const userXId = + loggedInUser.username === user.username ? undefined : user.id; navigation.push('Profile', { userXId, screenType, @@ -205,7 +206,6 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ usernameStyle = styles.searchResultUsername; nameStyle = styles.searchResultName; } - return ( <TouchableOpacity onPress={addToRecentlyStoredAndNavigateToProfile} @@ -257,9 +257,9 @@ const styles = StyleSheet.create({ discoverUsersContainer: { alignItems: 'center', textAlign: 'center', - margin: '0.5%', width: '32%', marginVertical: 10, + borderWidth: 1, }, searchResultAvatar: { height: 60, @@ -290,6 +290,7 @@ const styles = StyleSheet.create({ discoverUsersNameContainer: { justifyContent: 'space-evenly', alignSelf: 'stretch', + marginTop: 5, }, searchResultUsername: { fontSize: 18, diff --git a/src/components/search/Explore.tsx b/src/components/search/Explore.tsx index a02205a4..c07c66b8 100644 --- a/src/components/search/Explore.tsx +++ b/src/components/search/Explore.tsx @@ -1,27 +1,18 @@ import React from 'react'; -import {View, StyleSheet} from 'react-native'; +import {StyleSheet, Text, View} from 'react-native'; +import {useSelector} from 'react-redux'; +import {EXPLORE_SECTION_TITLES} from '../../constants'; +import {RootState} from '../../store/rootReducer'; +import {ExploreSectionType} from '../../types'; import ExploreSection from './ExploreSection'; const Explore: React.FC = () => { - const sections: Array<string> = [ - 'People you follow', - 'People you may know', - 'Trending in sports', - 'Trending on Tagg', - 'Trending in music', - ]; - const users: Array<string> = [ - 'Sam Davis', - 'Becca Smith', - 'Ann Taylor', - 'Clara Johnson', - 'Sarah Jung', - 'Lila Hernandez', - ]; + const {explores} = useSelector((state: RootState) => state.taggUsers); return ( <View style={styles.container}> - {sections.map((title) => ( - <ExploreSection key={title} title={title} users={users} /> + <Text style={styles.header}>Search Profiles</Text> + {EXPLORE_SECTION_TITLES.map((title: ExploreSectionType) => ( + <ExploreSection key={title} title={title} users={explores[title]} /> ))} </View> ); @@ -30,6 +21,14 @@ const Explore: React.FC = () => { const styles = StyleSheet.create({ container: { zIndex: 0, + // margin: '5%', + }, + header: { + fontWeight: '700', + fontSize: 22, + color: '#fff', + marginBottom: '2%', + margin: '5%', }, }); export default Explore; diff --git a/src/components/search/ExploreSection.tsx b/src/components/search/ExploreSection.tsx index 8e826bd9..8e8b4988 100644 --- a/src/components/search/ExploreSection.tsx +++ b/src/components/search/ExploreSection.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import {View, Text, ScrollView, StyleSheet} from 'react-native'; +import React, {Fragment} from 'react'; +import {ScrollView, StyleSheet, Text, View} from 'react-native'; +import {ProfilePreviewType} from '../../types'; import ExploreSectionUser from './ExploreSectionUser'; /** @@ -9,33 +10,40 @@ import ExploreSectionUser from './ExploreSectionUser'; interface ExploreSectionProps { title: string; - users: Array<string>; + users: ProfilePreviewType[]; } const ExploreSection: React.FC<ExploreSectionProps> = ({title, users}) => { - return ( + return users.length !== 0 ? ( <View style={styles.container}> <Text style={styles.header}>{title}</Text> <ScrollView horizontal showsHorizontalScrollIndicator={false}> - {users.map((name, key) => ( - <ExploreSectionUser {...{name, key}} style={styles.user} /> + <View style={styles.padding} /> + {users.map((user) => ( + <ExploreSectionUser key={user.id} user={user} style={styles.user} /> ))} </ScrollView> </View> + ) : ( + <Fragment /> ); }; const styles = StyleSheet.create({ container: { - marginBottom: 30, + marginVertical: '5%', }, header: { fontWeight: '600', fontSize: 20, color: '#fff', - marginBottom: 20, + marginLeft: '5%', + marginBottom: '5%', }, user: { - marginHorizontal: 15, + marginHorizontal: 5, + }, + padding: { + width: 10, }, }); diff --git a/src/components/search/ExploreSectionUser.tsx b/src/components/search/ExploreSectionUser.tsx index a9fce063..0bf68a20 100644 --- a/src/components/search/ExploreSectionUser.tsx +++ b/src/components/search/ExploreSectionUser.tsx @@ -1,12 +1,18 @@ -import React from 'react'; +import {useNavigation} from '@react-navigation/native'; +import React, {useEffect, useState} from 'react'; import { + Image, StyleSheet, Text, - ViewProps, - Image, TouchableOpacity, + ViewProps, } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; +import {useDispatch, useSelector, useStore} from 'react-redux'; +import {loadAvatar} from '../../services'; +import {RootState} from '../../store/rootReducer'; +import {ProfilePreviewType, ScreenType} from '../../types'; +import {fetchUserX, userXInStore} from '../../utils'; /** * Search Screen for user recommendations and a search @@ -14,14 +20,52 @@ import LinearGradient from 'react-native-linear-gradient'; */ interface ExploreSectionUserProps extends ViewProps { - name: string; + user: ProfilePreviewType; } const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({ - name, + user, style, }) => { + const {id, username, first_name, last_name} = user; + const [avatar, setAvatar] = useState<string | null>(null); + const navigation = useNavigation(); + const {user: loggedInUser} = useSelector((state: RootState) => state.user); + const state: RootState = useStore().getState(); + const screenType = ScreenType.Search; + + const dispatch = useDispatch(); + + useEffect(() => { + let mounted = true; + const loadAvatarImage = async () => { + const response = await loadAvatar(id, true); + if (mounted) { + setAvatar(response); + } + }; + loadAvatarImage(); + return () => { + mounted = false; + }; + }, [user]); + + const handlePress = async () => { + if (!userXInStore(state, screenType, user.id)) { + await fetchUserX( + dispatch, + {userId: user.id, username: user.username}, + screenType, + ); + } + const userXId = loggedInUser.username === user.username ? undefined : id; + navigation.push('Profile', { + userXId, + screenType, + }); + }; + return ( - <TouchableOpacity style={[styles.container, style]}> + <TouchableOpacity style={[styles.container, style]} onPress={handlePress}> <LinearGradient colors={['#9F00FF', '#27EAE9']} useAngle @@ -29,12 +73,18 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({ angleCenter={{x: 0.5, y: 0.5}} style={styles.gradient}> <Image - source={require('../../assets/images/avatar-placeholder.png')} + source={ + avatar + ? {uri: avatar} + : require('../../assets/images/avatar-placeholder.png') + } style={styles.profile} /> </LinearGradient> - <Text style={styles.name}>{name}</Text> - <Text style={styles.username}>{`@${name.split(' ').join('')}`}</Text> + <Text style={styles.name} numberOfLines={2}> + {first_name} {last_name} + </Text> + <Text style={styles.username} numberOfLines={1}>{`@${username}`}</Text> </TouchableOpacity> ); }; @@ -42,27 +92,30 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({ const styles = StyleSheet.create({ container: { alignItems: 'center', + width: 100, }, gradient: { - height: 80, - width: 80, + height: 60, + aspectRatio: 1, borderRadius: 40, justifyContent: 'center', alignItems: 'center', marginBottom: 10, }, profile: { - height: 76, - width: 76, + height: 55, + aspectRatio: 1, borderRadius: 38, }, name: { fontWeight: '600', + flexWrap: 'wrap', fontSize: 16, color: '#fff', + textAlign: 'center', }, username: { - fontWeight: '600', + fontWeight: '400', fontSize: 14, color: '#fff', }, diff --git a/src/constants/api.ts b/src/constants/api.ts index 639bc8f8..de43b94d 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -27,6 +27,7 @@ export const BLOCK_USER_ENDPOINT: string = API_URL + 'block/'; export const PASSWORD_RESET_ENDPOINT: string = API_URL + 'password-reset/'; export const MOMENT_CATEGORY_ENDPOINT: string = API_URL + 'moment-category/'; export const NOTIFICATIONS_ENDPOINT: string = API_URL + 'notifications/'; +export const DISCOVER_ENDPOINT: string = API_URL + 'discover/'; export const WAITLIST_USER_ENDPOINT: string = API_URL + 'waitlist-user/'; // Register as FCM device diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 90d70724..b96d9438 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -1,5 +1,5 @@ import {ReactText} from 'react'; -import {BackgroundGradientType} from './../types/'; +import {BackgroundGradientType, ExploreSectionType} from './../types/'; import {SCREEN_WIDTH, SCREEN_HEIGHT} from '../utils'; export const CHIN_HEIGHT = 34; @@ -160,3 +160,12 @@ export const MOMENT_CATEGORY_BG_COLORS: string[] = [ '#365F6A', '#4E7175', ]; + +export const EXPLORE_SECTION_TITLES: ExploreSectionType[] = [ + 'New to Tagg', + 'People You May Know', + 'Trending on Tagg', + "Brown '21", + "Brown '22", + "Brown '23", +]; diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx index 78c0c5cc..4505163c 100644 --- a/src/screens/search/SearchScreen.tsx +++ b/src/screens/search/SearchScreen.tsx @@ -1,9 +1,17 @@ import AsyncStorage from '@react-native-community/async-storage'; -import React, {useEffect, useState} from 'react'; -import {Keyboard, ScrollView, StatusBar, StyleSheet} from 'react-native'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useEffect, useState} from 'react'; +import { + Keyboard, + RefreshControl, + ScrollView, + StatusBar, + StyleSheet, +} from 'react-native'; import Animated, {Easing, timing} from 'react-native-reanimated'; +import {useDispatch, useSelector} from 'react-redux'; import { - DiscoverUsers, + Explore, RecentSearches, SearchBackground, SearchBar, @@ -13,6 +21,8 @@ import { TabsGradient, } from '../../components'; import {SEARCH_ENDPOINT, TAGG_TEXT_LIGHT_BLUE} from '../../constants'; +import {loadRecentlySearched, resetScreenType} from '../../store/actions'; +import {RootState} from '../../store/rootReducer'; import {ProfilePreviewType, ScreenType, UserType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; const NO_USER: UserType = { @@ -20,18 +30,13 @@ const NO_USER: UserType = { username: '', }; -import {RootState} from '../../store/rootReducer'; -import {useSelector, useDispatch} from 'react-redux'; -import {resetScreenType} from '../../store/actions'; -import {useFocusEffect} from '@react-navigation/native'; - /** * Search Screen for user recommendations and a search * tool to allow user to find other users */ const SearchScreen: React.FC = () => { - const {recentSearches, taggUsers} = useSelector( + const {recentSearches, explores} = useSelector( (state: RootState) => state.taggUsers, ); const [query, setQuery] = useState<string>(''); @@ -42,6 +47,19 @@ const SearchScreen: React.FC = () => { const [searching, setSearching] = useState(false); const top = Animated.useValue(-SCREEN_HEIGHT); const [user, setUser] = useState<UserType>(NO_USER); + const [refreshing, setRefreshing] = useState<boolean>(false); + + const dispatch = useDispatch(); + + const onRefresh = useCallback(() => { + const refrestState = async () => { + dispatch(loadRecentlySearched()); + }; + setRefreshing(true); + refrestState().then(() => { + setRefreshing(false); + }); + }, []); useEffect(() => { if (query.length < 3) { @@ -76,8 +94,6 @@ const SearchScreen: React.FC = () => { loadResults(query); }, [query]); - const dispatch = useDispatch(); - /** * Code under useFocusEffect gets executed every time the screen comes under focus / is being viewed by the user. * This is done to reset the users stored in our store for the Search screen. @@ -135,7 +151,10 @@ const SearchScreen: React.FC = () => { keyboardShouldPersistTaps={'always'} stickyHeaderIndices={[4]} contentContainerStyle={styles.contentContainer} - showsVerticalScrollIndicator={false}> + showsVerticalScrollIndicator={false} + refreshControl={ + <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> + }> <SearchHeader style={styles.header} {...{top}} /> <SearchBar style={styles.searchBar} @@ -146,13 +165,7 @@ const SearchScreen: React.FC = () => { value={query} {...{top, searching}} /> - {/* Removed for Alpha for now */} - {/* <Explore /> */} - <DiscoverUsers - sectionTitle="Discover Users" - users={taggUsers} - screenType={ScreenType.Search} - /> + <Explore /> <SearchResultsBackground {...{top}}> {results.length === 0 && recents.length !== 0 ? ( <RecentSearches diff --git a/src/services/ExploreServices.ts b/src/services/ExploreServices.ts index 2181ea7d..ca4f1b69 100644 --- a/src/services/ExploreServices.ts +++ b/src/services/ExploreServices.ts @@ -1,4 +1,8 @@ -import {ALL_USERS_ENDPOINT} from '../constants'; +import AsyncStorage from '@react-native-community/async-storage'; +import {getDeviceToken} from 'react-native-device-info'; +import {ALL_USERS_ENDPOINT, DISCOVER_ENDPOINT} from '../constants'; +import {EMPTY_EXPLORE_SECTIONS} from '../store/initialStates'; +import {ExploreSectionType, ProfilePreviewType} from '../types'; export const getAllTaggUsers = async (token: string) => { try { @@ -26,3 +30,31 @@ export const getAllTaggUsers = async (token: string) => { ); } }; + +export const getAllExploreSections = async () => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(DISCOVER_ENDPOINT, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + if (response.status !== 200) { + return EMPTY_EXPLORE_SECTIONS; + } + const data = await response.json(); + const exploreSections: Record<ExploreSectionType, ProfilePreviewType[]> = { + 'New to Tagg': data.categories.new_to_tagg, + 'People You May Know': data.categories.people_you_may_know, + 'Trending on Tagg': data.categories.trending_on_tagg, + "Brown '21": data.categories.brown_21, + "Brown '22": data.categories.brown_22, + "Brown '23": data.categories.brown_23, + }; + + return exploreSections; + } catch (error) { + console.log('Unable to fetch explore data'); + } +}; diff --git a/src/store/actions/taggUsers.ts b/src/store/actions/taggUsers.ts index 7f841c51..7b6d4d5e 100644 --- a/src/store/actions/taggUsers.ts +++ b/src/store/actions/taggUsers.ts @@ -1,8 +1,7 @@ -import {RootState} from '../rootReducer'; -import {loadRecentlySearchedUsers, getAllTaggUsers} from '../../services'; import {Action, ThunkAction} from '@reduxjs/toolkit'; +import {getAllExploreSections, loadRecentlySearchedUsers} from '../../services'; import {taggUsersFetched} from '../reducers'; -import {getTokenOrLogout} from '../../utils'; +import {RootState} from '../rootReducer'; export const loadRecentlySearched = (): ThunkAction< Promise<void>, @@ -11,12 +10,11 @@ export const loadRecentlySearched = (): ThunkAction< Action<string> > => async (dispatch) => { try { - const token = await getTokenOrLogout(dispatch); const recentSearches = await loadRecentlySearchedUsers(); - const taggUsers = await getAllTaggUsers(token); + const exploreSections = await getAllExploreSections(); dispatch({ type: taggUsersFetched.type, - payload: {recentSearches, taggUsers}, + payload: {recentSearches, explores: exploreSections}, }); } catch (error) { console.log(error); diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index de97b129..87e1ce22 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -1,4 +1,5 @@ import { + ExploreSectionType, MomentType, NotificationType, ProfilePreviewType, @@ -58,9 +59,21 @@ export const NO_SOCIAL_ACCOUNTS: Record<string, SocialAccountType> = { Twitter: {posts: []}, }; +export const EMPTY_EXPLORE_SECTIONS: Record< + ExploreSectionType, + ProfilePreviewType[] +> = { + 'People You May Know': EMPTY_PROFILE_PREVIEW_LIST, + 'New to Tagg': EMPTY_PROFILE_PREVIEW_LIST, + 'Trending on Tagg': EMPTY_PROFILE_PREVIEW_LIST, + "Brown '21": EMPTY_PROFILE_PREVIEW_LIST, + "Brown '22": EMPTY_PROFILE_PREVIEW_LIST, + "Brown '23": EMPTY_PROFILE_PREVIEW_LIST, +}; + export const NO_TAGG_USERS = { recentSearches: EMPTY_PROFILE_PREVIEW_LIST, - taggUsers: EMPTY_PROFILE_PREVIEW_LIST, + explores: EMPTY_EXPLORE_SECTIONS, }; export const NO_SOCIALS = { diff --git a/src/store/reducers/taggUsersReducer.ts b/src/store/reducers/taggUsersReducer.ts index ff30f7a0..33e2e18d 100644 --- a/src/store/reducers/taggUsersReducer.ts +++ b/src/store/reducers/taggUsersReducer.ts @@ -6,8 +6,8 @@ const taggUsersSlice = createSlice({ initialState: NO_TAGG_USERS, reducers: { taggUsersFetched: (state, action) => { - state.recentSearches = action.payload.taggUsers; - state.taggUsers = action.payload.taggUsers; + state.recentSearches = action.payload.recentSearches; + state.explores = action.payload.explores; }, }, }); diff --git a/src/types/types.ts b/src/types/types.ts index 10e5de9a..093adbe4 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -103,6 +103,14 @@ export enum ScreenType { Notifications, } +export type ExploreSectionType = + | 'People You May Know' + | 'New to Tagg' + | 'Trending on Tagg' + | "Brown '21" + | "Brown '22" + | "Brown '23"; + /** * Redux store to have a Record of ScreenType (Search, Profile, Home etc) mapped to * A Record of userIXd mapped to UserXType |