Skip to content

Commit

Permalink
Speaker Page (#64)
Browse files Browse the repository at this point in the history
* Speaker Page
Fixes #22

* [feat] : change title based on person type ( organizer or speaker )

* update react native version

* [feat] : add border color on img

* [feat] : add correct border color

* [chore] : fine tune

* speaker details from api, fixed styling

* new event slug, handle empty feed, twitter update

* speaker page scrollview

* empty feed in dark mode

* changed [speaker] page to [profile]

* hide sponsors card

---------

Co-authored-by: brianwachira <[email protected]>
  • Loading branch information
kharioki and brianwachira authored Oct 29, 2023
1 parent f8eef81 commit 9812050
Show file tree
Hide file tree
Showing 20 changed files with 303 additions and 95 deletions.
3 changes: 1 addition & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
EXPO_PUBLIC_API_AUTHORIZATION_KEY="some key"
EXPO_PUBLIC_DEV_BASE_URL="https://dev.droidcon.co.ke/api/v1"
EXPO_PUBLIC_PROD_BASE_URL="https://api.droidcon.co.ke/v1"
EXPO_PUBLIC_BASE_URL="https://api.droidcon.co.ke/v1"
212 changes: 212 additions & 0 deletions app/(app)/[profile].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { AntDesign, FontAwesome5 } from '@expo/vector-icons';
import { useTheme } from '@react-navigation/native';
import { Image } from 'expo-image';
import * as Linking from 'expo-linking';
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { Dimensions, ImageBackground, Pressable, ScrollView, StyleSheet, View } from 'react-native';
import Row from '../../components/common/Row';
import Space from '../../components/common/Space';
import StyledText from '../../components/common/StyledText';
import MainContainer from '../../components/container/MainContainer';
import { usePrefetchedEventData } from '../../services/api';
import { getTwitterHandle } from '../../util/helpers';

type Profile = {
image?: string;
bio?: string;
twitter_handle?: string | null;
tagline?: string;
name?: string;
};

const { height } = Dimensions.get('window');

const Speaker = () => {
const { colors, dark } = useTheme();
const router = useRouter();
const { name, type } = useLocalSearchParams();
const [details, setDetails] = useState<Profile>({});

const { speakers, organizingTeam } = usePrefetchedEventData();

useEffect(() => {
if (type === 'speaker') {
const speaker = speakers?.data.filter((x) => x.name === name)[0];
setDetails({
image: speaker?.avatar,
bio: speaker?.biography,
twitter_handle: speaker?.twitter,
tagline: speaker?.tagline,
name: speaker?.name,
});
} else if (type === 'organizer') {
const organizer = organizingTeam?.data.filter((x) => x.name === name)[0];
setDetails({
image: organizer?.photo,
bio: organizer?.bio,
twitter_handle: organizer?.twitter_handle,
tagline: organizer?.tagline,
name: organizer?.name,
});
}
}, [name, organizingTeam?.data, speakers?.data, type]);

const handleTwitterProfile = (link: string | null | undefined) => {
if (link) {
Linking.openURL(`${link}`);
}
};

return (
<MainContainer preset="fixed">
<Stack.Screen
options={{
title: type === 'speaker' ? 'Speaker' : 'Organizer',
headerTitleAlign: 'left',
headerLeft: () => (
<AntDesign name="arrowleft" size={24} color={colors.whiteConstant} onPress={() => router.back()} />
),
}}
/>
<ScrollView style={styles.main}>
<ImageBackground
source={
dark ? require('../../assets/images/speaker-dark.png') : require('../../assets/images/speaker-light.png')
}
style={styles.banner}
resizeMode="cover"
/>

<View style={styles.centered}>
<View style={styles.avatar}>
<Image
source={details?.image}
style={[styles.image, { borderColor: colors.tint }]}
contentFit="contain"
transition={1000}
/>
</View>
<View style={styles.info}>
<View style={styles.row}>
{type === 'speaker' && <FontAwesome5 name="android" size={18} color={colors.tertiary} />}
<StyledText size="sm" font="regular" style={{ color: colors.tertiary }}>
{type === 'speaker' ? 'Speaker' : 'Organizer'}
</StyledText>
</View>
<StyledText size="lg" font="bold" variant="link">
{name}
</StyledText>

<Space size={10} />

<View style={styles.taglineContainer}>
<StyledText size="base" font="regular" style={[styles.text, { color: colors.textLight }]}>
{details?.tagline}
</StyledText>
</View>
</View>
</View>

<Space size={10} />

<View style={styles.wrapper}>
<StyledText size="lg" font="bold" variant="link">
Bio
</StyledText>

<Space size={10} />

<StyledText size="base" font="light" style={styles.contentText}>
{details?.bio}
</StyledText>
</View>
</ScrollView>

<View style={[styles.socialLink, { borderTopColor: colors.accent }]}>
<Row>
<StyledText font="medium">Twitter Handle</StyledText>

<Pressable
style={[styles.button, { borderColor: colors.primary, backgroundColor: colors.background }]}
onPress={() => handleTwitterProfile(details?.twitter_handle)}
>
<FontAwesome5 name="twitter" size={20} color={colors.primary} />
<Space size={4} horizontal />
<StyledText font="medium" style={{ color: colors.primary }}>
{details?.twitter_handle ? getTwitterHandle(details?.twitter_handle) : 'N/A'}
</StyledText>
</Pressable>
</Row>
</View>
</MainContainer>
);
};

const styles = StyleSheet.create({
main: {
flex: 1,
width: '100%',
},
banner: {
height: height / 5,
width: '100%',
},
centered: {
width: '100%',
alignItems: 'center',
},
wrapper: {
width: '100%',
top: -40,
paddingHorizontal: 20,
},
avatar: {
height: 100,
width: 100,
borderRadius: 60,
alignItems: 'center',
top: -50,
},
text: {
textAlign: 'center',
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 5,
},
image: {
height: 103,
width: 103,
borderWidth: 2,
borderRadius: 60,
},
info: {
top: -40,
alignItems: 'center',
},
contentText: {
lineHeight: 18,
},
socialLink: {
width: '100%',
borderTopWidth: 1,
padding: 20,
marginBottom: 20,
},
button: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-evenly',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
borderWidth: 1,
},
taglineContainer: {
width: '70%',
},
});

export default Speaker;
28 changes: 0 additions & 28 deletions app/(app)/[speaker].tsx

This file was deleted.

9 changes: 5 additions & 4 deletions app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ export default () => {
}}
/>
<Stack.Screen
name="[speaker]"
name="[profile]"
options={{
headerTintColor: colors.text,
headerStyle: {
backgroundColor: colors.background,
headerTintColor: colors.whiteConstant,
headerTransparent: true,
headerTitleStyle: {
fontWeight: 'normal',
},
}}
/>
Expand Down
52 changes: 21 additions & 31 deletions app/(app)/home/about.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useTheme } from '@react-navigation/native';
import { FlashList } from '@shopify/flash-list';
import { useQuery } from '@tanstack/react-query';
import { Image } from 'expo-image';
import { Stack, useRouter } from 'expo-router';
import React from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { StyleSheet, View } from 'react-native';
import OrganizerCard from '../../../components/cards/OrganizerCard';
import OrganizersCard from '../../../components/cards/OrganizersCard';
import Space from '../../../components/common/Space';
Expand All @@ -13,18 +11,12 @@ import MainContainer from '../../../components/container/MainContainer';
import HeaderRight from '../../../components/headers/HeaderRight';
import { WIDE_BLURHASH } from '../../../config/constants';
import type { OrganizingTeamMember } from '../../../global';
import { getOrganizingTeam, usePrefetchedEventData } from '../../../services/api';
import { usePrefetchedEventData } from '../../../services/api';

const About = () => {
const router = useRouter();
const { colors } = useTheme();

const { organizers } = usePrefetchedEventData();

const { isLoading, data: organizingTeam } = useQuery({
queryKey: ['organizingTeam'],
queryFn: getOrganizingTeam,
});
const { organizers, organizingTeam } = usePrefetchedEventData();

const organizingIndividuals = organizingTeam?.data.filter((item: OrganizingTeamMember) => item.type === 'individual');

Expand Down Expand Up @@ -81,26 +73,24 @@ const About = () => {
<Space size={20} />
</View>

{isLoading ? (
<ActivityIndicator size="large" color={colors.tertiary} />
) : (
<View style={styles.listContainer}>
<FlashList
data={organizingIndividuals}
numColumns={3}
renderItem={({ item }) => (
<OrganizerCard
name={item.name}
photo={item.photo}
tagline={item.tagline}
handlePress={() => router.push({ pathname: `/${item.name}`, params: { name: item.name } })}
/>
)}
keyExtractor={(item: OrganizingTeamMember, index: number) => index.toString()}
estimatedItemSize={50}
/>
</View>
)}
<View style={styles.listContainer}>
<FlashList
data={organizingIndividuals}
numColumns={3}
renderItem={({ item }) => (
<OrganizerCard
name={item.name}
photo={item.photo}
tagline={item.tagline}
handlePress={() =>
router.push({ pathname: `/${item.name}`, params: { name: item.name, type: 'organizer' } })
}
/>
)}
keyExtractor={(item: OrganizingTeamMember, index: number) => index.toString()}
estimatedItemSize={50}
/>
</View>
</View>

{organizers && <OrganizersCard organizers={organizers} />}
Expand Down
16 changes: 14 additions & 2 deletions app/(app)/home/feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useTheme } from '@react-navigation/native';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import StyledText from '../../../../components/common/StyledText';
import MainContainer from '../../../../components/container/MainContainer';
import FeedList from '../../../../components/lists/FeedList';
import { getEventFeed } from '../../../../services/api';
Expand All @@ -11,11 +12,21 @@ export default function Page() {

const { isLoading, data } = useQuery({ queryKey: ['feed'], queryFn: getEventFeed });

if (isLoading) {
return (
<MainContainer style={styles.main}>
<View style={styles.centered}>
<ActivityIndicator size="large" color={colors.tertiary} />
</View>
</MainContainer>
);
}

return (
<MainContainer style={styles.main}>
{isLoading ? (
{data?.data && data?.data.length < 1 ? (
<View style={styles.centered}>
<ActivityIndicator size="large" color={colors.tertiary} />
<StyledText>This feed is empty for now.</StyledText>
</View>
) : (
<View>{data && <FeedList feed={data} />}</View>
Expand All @@ -26,6 +37,7 @@ export default function Page() {

const styles = StyleSheet.create({
main: {
flex: 1,
paddingHorizontal: 0,
},
centered: {
Expand Down
Loading

0 comments on commit 9812050

Please sign in to comment.