make event list work
This commit is contained in:
1 parent
5466b771ed
commit
75d2bb753c
11 files changed
+74
-113
No files matched your search
@@ -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
@@ -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=
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ const styles = StyleSheet.create({
|
||||
borderColor: theme.colors.neufPurple,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
backgroundColor: 'white',
|
||||
padding: 4,
|
||||
marginVertical: 4,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -8,8 +8,6 @@ export type EventStackParamList = {
|
||||
EventDetail: { event: EventType };
|
||||
};
|
||||
|
||||
export type EventDetailProps = NativeStackScreenProps<EventStackParamList, 'EventDetail'>;
|
||||
|
||||
export type UserStackParamList = {
|
||||
Membership: undefined;
|
||||
Login: undefined;
|
||||
|
||||
Reference in new issue
Block a user