Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 191 additions & 65 deletions client/src/pages/Events.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,46 @@ const Events = () => {
const [filteredEvents, setFilteredEvents] = useState([]);
const [selectedClub, setSelectedClub] = useState("All");
const [searchTerm, setSearchTerm] = useState("");
const [upcomingEvents, setUpcomingEvents] = useState([]);
const [pastEvents, setPastEvents] = useState([]);

// Helper function to parse date and get latest date for sorting
const getEventDate = (dates) => {
if (!dates || dates.length === 0) return new Date();

// Get the latest date from the dates array for sorting
const latestDate = dates[dates.length - 1];
const [day, month, year] = latestDate.split('-');
return new Date(year, month - 1, day);
};
Comment on lines +13 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correctness: getEventDate returns new Date() (current date) if dates is empty or falsy, causing all such events to be treated as upcoming and possibly misclassified.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In client/src/pages/Events.jsx, lines 13-20, the `getEventDate` function returns `new Date()` (the current date) if `dates` is empty or falsy. This causes events with missing/empty dates to be misclassified as upcoming. Change the function to return `null` in this case, and ensure all usages of `getEventDate` handle `null` appropriately.
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const getEventDate = (dates) => {
if (!dates || dates.length === 0) return new Date();
// Get the latest date from the dates array for sorting
const latestDate = dates[dates.length - 1];
const [day, month, year] = latestDate.split('-');
return new Date(year, month - 1, day);
};
const getEventDate = (dates) => {
if (!dates || dates.length === 0) return null;
// Get the latest date from the dates array for sorting
const latestDate = dates[dates.length - 1];
const [day, month, year] = latestDate.split('-');
return new Date(year, month - 1, day);
};

Comment on lines +13 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performance: The function getEventDate is called multiple times per event (in sorting, filtering, and rendering), leading to redundant date parsing and computation for each event.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In client/src/pages/Events.jsx, lines 13-20, 42-43, 76-77, and 319, the function `getEventDate` is called multiple times per event (during sorting, filtering, and rendering), causing redundant date parsing and computation. Optimize by computing and storing the parsed event date once per event (e.g., as a property on the event object) during initial processing, and reuse it throughout the component.


// Helper function to check if event is past
const isEventPast = (dates) => {
if (!dates || dates.length === 0) return false;

const eventDate = getEventDate(dates);
const today = new Date();
today.setHours(0, 0, 0, 0); // Reset time for accurate date comparison

return eventDate < today;
};

// Fetch events data
useEffect(() => {
const fetchEvents = async () => {
try {
const response = await fetch('/data/events.json');
const data = await response.json();
setEvents(data.events);
setFilteredEvents(data.events);

// Sort events by date (earliest first)
const sortedEvents = data.events.sort((a, b) => {
const dateA = getEventDate(a.dates);
const dateB = getEventDate(b.dates);
return dateA - dateB;
});
Comment on lines +41 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performance: data.events.sort(...) mutates the original array in-place, causing side effects if data.events is reused elsewhere and breaking referential integrity for React state updates.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In client/src/pages/Events.jsx, lines 41-45, the code mutates the original `data.events` array in-place with `.sort()`, which can cause side effects and break React state update patterns. Refactor to use `[...data.events].sort(...)` to avoid mutating the original array.
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const sortedEvents = data.events.sort((a, b) => {
const dateA = getEventDate(a.dates);
const dateB = getEventDate(b.dates);
return dateA - dateB;
});
const sortedEvents = [...data.events].sort((a, b) => {
const dateA = getEventDate(a.dates);
const dateB = getEventDate(b.dates);
return dateA - dateB;
});


setEvents(sortedEvents);
setFilteredEvents(sortedEvents);
} catch (error) {
console.error('Failed to fetch events:', error);
}
Expand All @@ -25,7 +56,7 @@ const Events = () => {
// Get unique clubs for filter
const clubs = ["All", ...new Set(events.map(event => event.clubName))];

// Filter events based on club and search term
// Filter events and separate into past and upcoming
useEffect(() => {
let filtered = events;

Expand All @@ -41,6 +72,12 @@ const Events = () => {
);
}

// Separate into past and upcoming events
const upcoming = filtered.filter(event => !isEventPast(event.dates));
const past = filtered.filter(event => isEventPast(event.dates));
Comment on lines +76 to +77
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performance: The events are filtered twice for past/upcoming in every useEffect, causing O(n) work twice per render; this can be combined into a single pass for better efficiency on large datasets.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In client/src/pages/Events.jsx, lines 76-77, the code filters the events array twice to separate past and upcoming events, resulting in two O(n) passes. Refactor to a single loop that pushes each event into either the `upcoming` or `past` array for better performance on large datasets.
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const upcoming = filtered.filter(event => !isEventPast(event.dates));
const past = filtered.filter(event => isEventPast(event.dates));
const upcoming = [];
const past = [];
for (const event of filtered) {
if (isEventPast(event.dates)) {
past.push(event);
} else {
upcoming.push(event);
}
}


setUpcomingEvents(upcoming);
setPastEvents(past.reverse()); // Show most recent past events first
setFilteredEvents(filtered);
}, [selectedClub, searchTerm, events]);

Expand Down Expand Up @@ -145,13 +182,13 @@ const Events = () => {

{/* Results Count */}
<div className="text-sm text-gray-600 whitespace-nowrap">
{filteredEvents.length} event{filteredEvents.length !== 1 ? 's' : ''} found
{upcomingEvents.length} upcoming • {pastEvents.length} past events
</div>
</div>
</div>
</div>

{/* Events Grid */}
{/* Events Sections */}
<div className="max-w-6xl mx-auto px-4 sm:px-6 pb-16">
{filteredEvents.length === 0 ? (
<div className="text-center py-16">
Expand All @@ -160,74 +197,163 @@ const Events = () => {
<p className="text-gray-500">Try adjusting your search or filter criteria</p>
</div>
) : (
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEvents.map((event, index) => (
<motion.div
key={event.id}
className="bg-white rounded-2xl overflow-hidden shadow-lg hover:shadow-xl transition-all duration-500 hover:-translate-y-2 border-t-4 h-full"
style={{ borderTopColor: getClubColor(event.clubName) }}
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ scale: 1.02 }}
>
<div className="p-6 flex flex-col h-full">
{/* Event Header */}
<div className="flex items-start justify-between mb-4">
<span
className="text-xs font-semibold px-3 py-1 rounded-full text-white"
style={{ backgroundColor: getClubColor(event.clubName) }}
>
{event.clubName}
</span>
<div className="text-right">
<div className="text-sm font-semibold" style={{ color: '#1F2647' }}>
{event.date}
</div>
<div className="text-xs text-gray-500">
{formatDate(event.dates)}
</div>
</div>
<>
{/* Upcoming Events Section */}
{upcomingEvents.length > 0 ? (
<div className="mb-12">
<div className="flex items-center mb-8">
<div className="flex items-center">
<div className="w-3 h-3 rounded-full mr-3" style={{ backgroundColor: '#0D9488' }}></div>
<h2 className="text-2xl sm:text-3xl font-bold" style={{ color: '#1F2647' }}>
Upcoming Events
</h2>
</div>
<div className="flex-grow ml-4 h-px bg-gradient-to-r from-teal-300 to-transparent"></div>
<span className="ml-4 text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full">
{upcomingEvents.length} events
</span>
</div>

<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{upcomingEvents.map((event, index) => (
<EventCard
key={event.id}
event={event}
index={index}
getClubColor={getClubColor}
formatDate={formatDate}
isPast={false}
/>
))}
</div>
</div>
) : (
<div className="mb-12">
<div className="flex items-center mb-8">
<div className="flex items-center">
<div className="w-3 h-3 rounded-full mr-3" style={{ backgroundColor: '#0D9488' }}></div>
<h2 className="text-2xl sm:text-3xl font-bold" style={{ color: '#1F2647' }}>
Upcoming Events
</h2>
</div>
<div className="flex-grow ml-4 h-px bg-gradient-to-r from-teal-300 to-transparent"></div>
</div>

<div className="text-center py-12 bg-white rounded-2xl shadow-lg border-t-4" style={{ borderTopColor: '#0D9488' }}>
<div className="text-5xl mb-4">🎉</div>
<h3 className="text-xl font-semibold mb-2" style={{ color: '#1F2647' }}>No Upcoming Events</h3>
<p className="text-gray-600 mb-6">All events have concluded. Check back soon for new exciting events!</p>
<div className="text-sm text-gray-500">
Stay tuned to our social media for updates on future events
</div>
</div>
</div>
)}

{/* Event Title */}
<h3 className="text-xl font-bold mb-3 flex-shrink-0" style={{ color: '#1F2647' }}>
{event.eventName}
</h3>

{/* Event Description */}
<p className="text-gray-600 text-sm leading-relaxed flex-grow mb-4">
{event.description}
</p>

{/* Event Footer */}
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div className="flex items-center">
<div
className="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm"
style={{ backgroundColor: getClubColor(event.clubName) }}
>
📅
</div>
<span className="ml-2 text-sm font-medium" style={{ color: '#1F2647' }}>
Don't miss out!
</span>
</div>
<button
className="text-sm font-medium px-3 py-1 rounded-lg transition-colors duration-300 hover:bg-gray-100"
style={{ color: getClubColor(event.clubName) }}
>
Learn More
</button>
{/* Past Events Section */}
{pastEvents.length > 0 && (
<div>
<div className="flex items-center mb-8">
<div className="flex items-center">
<div className="w-3 h-3 rounded-full mr-3" style={{ backgroundColor: '#6B7280' }}></div>
<h2 className="text-2xl sm:text-3xl font-bold text-gray-600">
Past Events
</h2>
</div>
<div className="flex-grow ml-4 h-px bg-gradient-to-r from-gray-300 to-transparent"></div>
<span className="ml-4 text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full">
{pastEvents.length} events
</span>
</div>
</motion.div>
))}
</div>

<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{pastEvents.map((event, index) => (
<EventCard
key={event.id}
event={event}
index={index}
getClubColor={getClubColor}
formatDate={formatDate}
isPast={true}
/>
))}
</div>
</div>
)}
</>
)}
</div>
</div>
);
};

// Separate EventCard component for reusability
const EventCard = ({ event, index, getClubColor, formatDate, isPast }) => {
return (
<motion.div
className={`bg-white rounded-2xl overflow-hidden shadow-lg hover:shadow-xl transition-all duration-500 hover:-translate-y-2 border-t-4 h-full ${isPast ? 'opacity-75' : ''}`}
style={{ borderTopColor: getClubColor(event.clubName) }}
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: isPast ? 0.75 : 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ scale: 1.02, opacity: 1 }}
>
<div className="p-6 flex flex-col h-full">
{/* Event Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center">
<span
className="text-xs font-semibold px-3 py-1 rounded-full text-white"
style={{ backgroundColor: getClubColor(event.clubName) }}
>
{event.clubName}
</span>
{isPast && (
<span className="ml-2 text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded-full">
Past
</span>
)}
</div>
<div className="text-right">
<div className="text-xs text-gray-500">
{formatDate(event.dates)}
</div>
</div>
</div>

{/* Event Title */}
<h3 className="text-xl font-bold mb-3 flex-shrink-0" style={{ color: isPast ? '#6B7280' : '#1F2647' }}>
{event.eventName}
</h3>

{/* Event Description */}
<p className={`text-sm leading-relaxed flex-grow mb-4 ${isPast ? 'text-gray-500' : 'text-gray-600'}`}>
{event.description}
</p>

{/* Event Footer */}
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div className="flex items-center">
<div
className="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm"
style={{ backgroundColor: isPast ? '#6B7280' : getClubColor(event.clubName) }}
>
📅
</div>
<span className={`ml-2 text-sm font-medium ${isPast ? 'text-gray-500' : ''}`} style={{ color: isPast ? '#6B7280' : '#1F2647' }}>
{isPast ? 'Event completed' : "Don't miss out!"}
</span>
</div>
<button
className={`text-sm font-medium px-3 py-1 rounded-lg transition-colors duration-300 hover:bg-gray-100 ${isPast ? 'text-gray-500' : ''}`}
style={{ color: isPast ? '#6B7280' : getClubColor(event.clubName) }}
>
{isPast ? 'View Details' : 'Learn More'}
</button>
</div>
</div>
</motion.div>
);
};

Comment on lines 197 to +358
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performance: The main component function is over 350 lines and contains substantial logic and rendering, making it difficult to maintain and reason about; this is a major maintainability issue.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In client/src/pages/Events.jsx, lines 1-358, the main Events component is very large and contains substantial logic, state, and rendering, making it hard to maintain. Refactor by extracting major sections (such as filtering logic, event section rendering, and helper functions) into separate components or hooks to improve maintainability and readability.

export default Events;
Loading