Skip to content

Commit dba1978

Browse files
Create custom events list
1 parent c9bf062 commit dba1978

File tree

4 files changed

+189
-4
lines changed

4 files changed

+189
-4
lines changed

src/components/Community.tsx

+18-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import React, { useState, useEffect } from 'react';
22
import CustomCalendar from './Calendar';
33
import UpcomingEvents from './UpcomingEvents';
4+
import EventList from './EventList';
45
import { fetchEvents } from '../utils/events';
6+
import { EventData, useCsvParser } from '../hooks/CsvParser';
57

68
const Community: React.FC = () => {
7-
const [events, setEvents] = useState([]);
9+
const [events, setEvents] = useState<EventData[]>([]);
10+
const [isLoading, setIsLoading] = useState<boolean>(true);
11+
const [error, setError] = useState<string | null>(null);
812

913
useEffect(() => {
1014
const loadEvents = async () => {
11-
const eventsData = await fetchEvents();
12-
setEvents(eventsData);
15+
try {
16+
const { events: csvEvents, loading, error } = useCsvParser('/events.csv')
17+
if (error) {
18+
setError(error);
19+
} else {
20+
setEvents(csvEvents);
21+
}
22+
setIsLoading(loading);
23+
} catch (err) {
24+
setError('Failed to fetch events');
25+
setIsLoading(false);
26+
}
1327
};
1428

1529
loadEvents();
@@ -22,7 +36,7 @@ const Community: React.FC = () => {
2236
Welcome to the community page! Here you can find information about our community events, forums, and how to get involved.
2337
</p>
2438
<CustomCalendar events={events} />
25-
<UpcomingEvents events={events} />
39+
<EventList csvFileName="/events.csv" />
2640
</div>
2741
);
2842
};

src/components/EventCard.tsx

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
import { EventData } from '../hooks/CsvParser';
3+
4+
interface EventCardProps extends EventData {}
5+
6+
const EventCard: React.FC<EventCardProps> = ({
7+
date,
8+
title,
9+
description,
10+
additionalInfo,
11+
beginnerFriendly,
12+
link
13+
}) => {
14+
// Handle both string and Date objects
15+
const eventDate = new Date(date);
16+
const month = eventDate.toLocaleString('default', { month: 'long' });
17+
const day = eventDate.getDate();
18+
const year = eventDate.getFullYear();
19+
20+
return (
21+
<article className='flex bg-white rounded-lg shadow-md overflow-hidden w-full mb-4'>
22+
{/* Date Section */}
23+
<header className='bg-blue-500 text-white p-4 flex flex-col items-center justify-center min-w-[100px]'>
24+
<time dateTime={eventDate.toISOString()}>
25+
<div className='text-xl font-bold text-white'>{month}</div>
26+
<div className='text-6xl font-bold leading-none text-white'>{day}</div>
27+
<div className='text-xl text-white'>{year}</div>
28+
</time>
29+
</header>
30+
31+
{/* Content Section */}
32+
<div className='flex-1 p-4 relative'>
33+
<div className='flex justify-between items-center mb-2'>
34+
<h3 className='text-xl font-semibold'>{title}</h3>
35+
{beginnerFriendly && (
36+
<span className='bg-green-200 text-green-800 px-3 py-1 rounded-full text-sm border border-green-300'>
37+
Beginner Friendly
38+
</span>
39+
)}
40+
</div>
41+
42+
<div className='bg-gray-100 rounded-lg p-4 mb-2'>
43+
<p className='text-gray-700'>{description}</p>
44+
{additionalInfo && (
45+
<p className='text-gray-600 mt-2'>{additionalInfo}</p>
46+
)}
47+
</div>
48+
49+
<div className='flex justify-end'>
50+
<a
51+
href={link}
52+
className='bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-6 rounded-lg'
53+
aria-label={`Attend ${title} event`}
54+
>
55+
Attend Event
56+
</a>
57+
</div>
58+
</div>
59+
</article>
60+
);
61+
};
62+
63+
export default EventCard;

src/components/EventList.tsx

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import EventCard from './EventCard';
3+
import { useCsvParser } from '../hooks/CsvParser';
4+
5+
6+
interface EventListProps {
7+
csvFileName: string;
8+
}
9+
10+
const EventList: React.FC<EventListProps> = ({ csvFileName }) => {
11+
const { events, loading, error } = useCsvParser(csvFileName);
12+
13+
if (loading) {
14+
return (
15+
<div className='max-w-3xl mx-auto p-4 text-center'>
16+
<div className='animate-pulse text-xl'>Loading events...</div>
17+
</div>
18+
);
19+
}
20+
21+
if (error) {
22+
return (
23+
<div className='max-w-3xl mx-auto p-4 text-center'>
24+
<div className='text-xl text-red-500'>Error: {error}</div>
25+
</div>
26+
);
27+
}
28+
29+
if (events.length === 0) {
30+
return (
31+
<div className='max-w-3xl mx-auto p-4 text-center'>
32+
<div className='text-xl'>No events found</div>
33+
</div>
34+
);
35+
}
36+
37+
return (
38+
<div className='max-w-3xl mx-auto p-4'>
39+
<div className='max-h-[600px] overflow-y-auto'>
40+
{events.map((event) => (
41+
<EventCard
42+
key={event.id}
43+
id={event.id}
44+
date={event.date}
45+
title={event.title}
46+
description={event.description}
47+
additionalInfo={event.additionalInfo}
48+
beginnerFriendly={event.beginnerFriendly}
49+
link={event.link}
50+
/>
51+
))}
52+
</div>
53+
</div>
54+
);
55+
};
56+
57+
export default EventList;

src/hooks/CsvParser.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useState, useEffect } from 'react';
2+
import Papa, { ParseResult, ParseError } from 'papaparse';
3+
4+
export interface EventData {
5+
id: string | number;
6+
date: string;
7+
title: string;
8+
description: string;
9+
additionalInfo?: string;
10+
beginnerFriendly?: boolean;
11+
link: string;
12+
}
13+
14+
export const useCsvParser = (csvFileName: string) => {
15+
const [events, setEvents] = useState<EventData[]>([]);
16+
const [loading, setLoading] = useState<boolean>(true);
17+
const [error, setError] = useState<string | null>(null);
18+
19+
useEffect(() => {
20+
const fetchCsvData = async () => {
21+
try {
22+
setLoading(true);
23+
const response = await fetch(csvFileName);
24+
const text = await response.text();
25+
26+
Papa.parse<EventData>(text, {
27+
header: true,
28+
dynamicTyping: true,
29+
skipEmptyLines: true,
30+
transformHeader: header =>
31+
header.toLowerCase().replace(/\s+/g, '_'),
32+
complete: (results: ParseResult<EventData>) => {
33+
setEvents(results.data as EventData[]);
34+
setLoading(false);
35+
},
36+
error: (error: Error) => {
37+
setError(error.message);
38+
setLoading(false);
39+
}
40+
});
41+
} catch (err) {
42+
setError('Failed to fetch or parse CSV file');
43+
setLoading(false);
44+
}
45+
};
46+
47+
fetchCsvData();
48+
}, [csvFileName]);
49+
50+
return { events, loading, error };
51+
};

0 commit comments

Comments
 (0)