make event list work

This commit is contained in:
ponas committed 2024-11-28 21:34:44 +01:00
1 parent 5466b771ed
commit 75d2bb753c
11 files changed
+74 -113

No files matched your search

+4 -4
View File
@@ -3,8 +3,8 @@
"preview": {
"distribution": "internal",
"env": {
"EXPO_PUBLIC_EVENT_API_URL": "https://neuf.no/api/v1/events/",
"EXPO_PUBLIC_DUSKEN_API_URL": "https://galtinn.neuf.no/",
"EXPO_PUBLIC_EVENT_API_URL": "https://neuf.no/api/v1/events",
"EXPO_PUBLIC_DUSKEN_API_URL": "https://galtinn.neuf.no",
"EXPO_PUBLIC_DUSKEN_FORGOT_PASSWORD_URL": "https://galtinn.neuf.no/auth/password_reset/",
"EXPO_PUBLIC_DUSKEN_PRIVACY_POLICY_URL": "https://galtinn.neuf.no/nb/personvern/",
"EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY": "pk_test_4SNAIZZczSJKZ0y2wrZI4Ngb"
@@ -12,8 +12,8 @@
},
"production": {
"env": {
"EXPO_PUBLIC_EVENT_API_URL": "https://neuf.no/api/v1/events/",
"EXPO_PUBLIC_DUSKEN_API_URL": "https://galtinn.neuf.no/",
"EXPO_PUBLIC_EVENT_API_URL": "https://neuf.no/api/v1/events",
"EXPO_PUBLIC_DUSKEN_API_URL": "https://galtinn.neuf.no",
"EXPO_PUBLIC_DUSKEN_FORGOT_PASSWORD_URL": "https://galtinn.neuf.no/auth/password_reset/",
"EXPO_PUBLIC_DUSKEN_PRIVACY_POLICY_URL": "https://galtinn.neuf.no/nb/personvern/",
"EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY": "pk_live_4SNAG73lw9kqFGb5u1s0yHhA",
+2 -2
View File
@@ -1,5 +1,5 @@
EXPO_PUBLIC_EVENT_API_URL=https://neuf.no/api/v1/events/
EXPO_PUBLIC_DUSKEN_API_URL=https://galtinn.neuf.no/
EXPO_PUBLIC_EVENT_API_URL=https://neuf.no/api/v1/events
EXPO_PUBLIC_DUSKEN_API_URL=https://galtinn.neuf.no
EXPO_PUBLIC_DUSKEN_FORGOT_PASSWORD_URL=https://galtinn.neuf.no/auth/password_reset/
EXPO_PUBLIC_DUSKEN_PRIVACY_POLICY_URL=https://galtinn.neuf.no/nb/personvern/
EXPO_PUBLIC_SENTRY_DSN=
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "cnapp",
"version": "2.0.0",
"version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cnapp",
"version": "2.0.0",
"version": "2.0.1",
"dependencies": {
"@apollo/client": "^3.10.8",
"@babel/plugin-transform-private-methods": "^7.24.7",
-12
View File
@@ -7,7 +7,6 @@ import IconEvent from 'assets/icons/event.svg';
import IconInfo from 'assets/icons/information-circle.svg';
import IconPerson from 'assets/icons/person.svg';
import About from 'src/modules/about/About';
import EventDetail from 'src/modules/events/EventDetail';
import EventList from 'src/modules/events/EventList';
import Login from 'src/modules/login/Login';
import Membership from 'src/modules/membership/Membership';
@@ -37,17 +36,6 @@ const EventStackScreen = () => (
title: 'Program',
}}
/>
<EventStack.Screen
name="EventDetail"
component={EventDetail}
options={({ route }) => ({
// TODO: make this not suck
title:
route.params.event.title.decoded.length < 40
? route.params.event.title.decoded
: `${route.params.event.title.decoded.substring(0, 30)}...`,
})}
/>
</EventStack.Navigator>
);
+1
View File
@@ -58,6 +58,7 @@ const styles = StyleSheet.create({
borderColor: theme.colors.neufPurple,
borderWidth: 1,
borderRadius: 4,
backgroundColor: 'white',
padding: 4,
marginVertical: 4,
},
+31 -38
View File
@@ -1,16 +1,7 @@
import { SimpleLineIcons as Icon } from '@expo/vector-icons';
import moment from 'moment';
import React from 'react';
import {
StyleSheet,
View,
Platform,
SectionList,
TouchableOpacity,
ActivityIndicator,
Text,
Linking,
} from 'react-native';
import { StyleSheet, View, Platform, FlatList, TouchableOpacity, Text, Linking } from 'react-native';
import { Button } from 'react-native-ui-lib';
import 'moment/locale/nb';
@@ -25,29 +16,37 @@ const formatTime = (time: string) => {
return moment(time).format('llll');
};
const renderSectionHeader = ({ section }: { section: { title: string } }) => (
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{section.title}</Text>
</View>
);
const Header = () => <View style={styles.listHeader} />;
const EventList = () => {
const { eventsSectioned, refreshing, handleRefresh, handleLoadMore, error, loading } = useEventList();
const { events, refreshing, handleRefresh, error } = useEventList();
const renderItem = ({ item }: { item: EventType }) => (
const renderItem = ({ item, index }: { item: EventType; index: number }) => (
<TouchableOpacity
onPress={() => {
return Linking.openURL(items.url);
return Linking.openURL(item.url);
}}
style={styles.listItem}
style={[styles.listItem, ...(index === (events.length -1) ? [styles.lastListItem] : [])]}
>
<View style={styles.row}>
<View style={{ flex: 5 }}>
<Text style={styles.listItemTitle} numberOfLines={1}>
{item.title}
</Text>
<Text style={styles.listItemTime}>{formatTime(item.nextOccurence.start)}</Text>
{item.subtitle && (
<Text style={styles.listItemSubtitle} numberOfLines={1}>
{item.subtitle}
</Text>
)}
<Text style={styles.listItemTime}>
{item.futureOccurrencesCount > 1 && 'Neste: '}
{formatTime(item.nextOccurrence.start)}
{item.nextOccurrence.venue &&
` ${item.nextOccurrence.venue.preposition} ${item.nextOccurrence.venue.title}`}
</Text>
{item.futureOccurrencesCount > 1 && (
<Text style={styles.listItemTime}>{`Se flere tider (${item.futureOccurrencesCount - 1})`}</Text>
)}
</View>
<View style={styles.arrow}>
<Icon name="arrow-right" style={{ color: theme.colors.neufBlack, marginTop: 3 }} />
@@ -56,15 +55,6 @@ const EventList = () => {
</TouchableOpacity>
);
// FIXME: Hoist component definition
// eslint-disable-next-line react/no-unstable-nested-components
const Footer = () =>
!loading && (
<View style={{ paddingVertical: 20 }}>
<ActivityIndicator color={theme.colors.neufOrange} />
</View>
);
if (error) {
return (
<View style={theme.card}>
@@ -81,17 +71,12 @@ const EventList = () => {
}
return (
<SectionList
sections={eventsSectioned}
<FlatList
data={events}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
keyExtractor={(item: EventType) => item.id}
refreshing={refreshing}
onRefresh={handleRefresh}
onEndReached={handleLoadMore}
onEndReachedThreshold={10}
initialNumToRender={10}
ListFooterComponent={Footer}
style={[theme.card, styles.list]}
ListHeaderComponent={Header}
/>
@@ -101,16 +86,23 @@ export default EventList;
const styles = StyleSheet.create({
listItem: {
backgroundColor: theme.colors.neufWhite,
backgroundColor: theme.colors.betongGray,
paddingTop: 10,
paddingBottom: 12,
paddingLeft: 12,
paddingRight: 12,
borderBottomWidth: 1,
borderBottomColor: theme.colors.neufGray,
borderBottomColor: theme.colors.neufPink,
},
lastListItem: {
borderBottomWidth: 0,
},
listItemTitle: {
fontSize: 16,
fontWeight: 'bold',
},
listItemSubtitle: {
fontSize: 16,
},
listItemTime: {
fontSize: 14,
@@ -140,6 +132,7 @@ const styles = StyleSheet.create({
},
listHeader: {
paddingTop: 8,
backgroundColor: theme.colors.neufGray,
},
arrow: {
flex: 1,
+6 -36
View File
@@ -22,39 +22,11 @@ const useEventList = () => {
const [loading, setLoading] = useState(false);
const [events, setEvents] = useState<EventType[]>([]);
const [eventsSectioned, setEventsSectioned] = useState<SectionedEvents[]>([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [error, setError] = useState<Error | unknown | null>(null);
const [refreshing, setRefreshing] = useState(false);
const toSectionFormat = (freshEvents: EventType[]) => {
const sectioned: Record<string, EventType[]> = {};
freshEvents.forEach((event) => {
const title = formatDate(event.start_time);
if (title in sectioned) {
sectioned[title].push(event);
} else {
sectioned[title] = [event];
}
});
return Object.entries(sectioned).map(([title, section]) => ({ data: section, title }));
};
const handleRefresh = () => {
setPage(1);
setRefreshing(true);
setTotalPages(1);
fetchEvents();
};
const handleLoadMore = () => {
if (page >= totalPages || loading) {
return;
}
setPage(page + 1);
fetchEvents();
};
@@ -62,19 +34,19 @@ const useEventList = () => {
const url = `${process.env.EXPO_PUBLIC_EVENT_API_URL}?view=compact-app`;
setLoading(true);
let eventData;
let response;
let events;
try {
const res = await fetchWithTimeout(url);
eventData = await res.json().events;
response = await res.json();
events = response.events;
} catch (err) {
setError(err);
setLoading(false);
return;
}
setTotalPages(Math.floor(eventData.total / 10));
setEvents(page === 1 ? eventData : [...events, ...eventData]);
setEventsSectioned(page === 1 ? toSectionFormat(eventData) : toSectionFormat([...events, ...eventData]));
setError(eventData.error || null);
setEvents(events);
setError(response.error || null);
setLoading(false);
setRefreshing(false);
};
@@ -86,10 +58,8 @@ const useEventList = () => {
return {
handleRefresh,
handleLoadMore,
error,
events,
eventsSectioned,
refreshing,
loading,
};
+1
View File
@@ -92,6 +92,7 @@ const styles = StyleSheet.create({
input: {
height: 40,
borderColor: theme.colors.neufPurple,
backgroundColor: 'white',
borderWidth: 1,
margin: 10,
padding: 10,
+7 -1
View File
@@ -6,7 +6,6 @@ const colors = {
success: '#5cb85c',
lightText: '#666',
neufPurple: '#720328',
neufPink: '#CD96AB',
neufGray: '#E5E2D7',
neufOrange: '#f58220',
neufBeige: '#F8BC90',
@@ -14,6 +13,13 @@ const colors = {
neufWhite: '#FFFFFF',
neufBlack: '#161516',
gray1: '#dddddd',
// 2024 colors
deepBrick: '#720328',
neufPink: '#cd96ab',
betongGray: '#e5e2d7',
goldenOrange: '#ff6915',
goldenBeige: '#f8bc90',
chateauBlue: '#0a0f6d',
};
const card = {
+20 -16
View File
@@ -1,21 +1,25 @@
export interface Venue {
id: string;
slug: string;
title: string;
preposition: string;
url: string;
}
export interface EventOccurrence {
id: string;
start: string;
end?: string;
venue: Venue;
}
export interface EventType {
id: string;
start_time: string;
end_time: string;
content: {
rendered: string;
};
venue: string;
title: {
decoded: string;
};
thumbnail: {
medium_large: string;
};
price_regular: string;
price_member: string;
ticket_url: string;
facebook_url: string;
title: string;
subtitle?: string;
url: string;
nextOccurrence: EventOccurrence;
futureOccurrencesCount: number;
}
export interface MembershipType {
-2
View File
@@ -8,8 +8,6 @@ export type EventStackParamList = {
EventDetail: { event: EventType };
};
export type EventDetailProps = NativeStackScreenProps<EventStackParamList, 'EventDetail'>;
export type UserStackParamList = {
Membership: undefined;
Login: undefined;