Skip to content
Draft
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
1 change: 1 addition & 0 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type Event = {
start?: Date;
location?: string;
description?: string;
isAllDay?: boolean;
};

export type WorkShopLink = { type: 'github' | 'video'; link: string };
Expand Down
35 changes: 35 additions & 0 deletions src/app/api/events/past/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ type CalendarEvent = {
description?: string;
start?: {
dateTime?: string;
date?: string;
};
end?: {
dateTime?: string;
date?: string;
};
}

Expand Down Expand Up @@ -110,9 +115,39 @@ function parseItems(list: CalendarEvent[]): Event[] {
toAdd.location = item.location;
}

// Handle both timed events (dateTime) and full-day events (date)
if (item.start?.dateTime) {
toAdd.start = new Date(item.start.dateTime);
toAdd.isAllDay = false;
res.push(toAdd);
} else if (item.start?.date) {
// For full-day events, create date at noon local time to avoid timezone shifts
const [startYear, startMonth, startDay] = item.start.date.split('-').map(Number);
const startDate = new Date(startYear, startMonth - 1, startDay, 12, 0, 0);

// Check if it's a multi-day event
if (item.end?.date) {
const [endYear, endMonth, endDay] = item.end.date.split('-').map(Number);
const endDate = new Date(endYear, endMonth - 1, endDay, 12, 0, 0);

// Create an event for each day in the range
const currentDate = new Date(startDate);
while (currentDate < endDate) {
const eventCopy: Event = {
...toAdd,
id: `${item.id}-${currentDate.getFullYear()}-${currentDate.getMonth()}-${currentDate.getDate()}`,
start: new Date(currentDate),
isAllDay: true,
};
res.push(eventCopy);
currentDate.setDate(currentDate.getDate() + 1);
}
} else {
// Single-day event
toAdd.start = startDate;
toAdd.isAllDay = true;
res.push(toAdd);
}
}
} catch (err) {
console.warn(`Failed to parse event: ${item.id}`, err);
Expand Down
37 changes: 36 additions & 1 deletion src/app/api/events/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ type CalendarEvent = {
location?: string;
start?: {
dateTime?: string;
date?: string;
};
end?: {
dateTime?: string;
date?: string;
};
};

Expand All @@ -37,7 +42,7 @@ export async function GET(request: Request) {
const yearParam = url.searchParams.get('year');
const monthParam = url.searchParams.get('month');

let apiUrl = `${endpoint}?key=${process.env.CALENDAR_API_KEY}`;
let apiUrl = `${endpoint}?key=${process.env.CALENDAR_API_KEY}&singleEvents=true&orderBy=startTime`;

if (yearParam && monthParam) {
const year = parseInt(yearParam, 10);
Expand Down Expand Up @@ -134,9 +139,39 @@ function parseItems(list: CalendarEvent[]): Event[] {
toAdd.location = item.location;
}

// Handle both timed events (dateTime) and full-day events (date)
if (item.start?.dateTime) {
toAdd.start = new Date(item.start.dateTime);
toAdd.isAllDay = false;
res.push(toAdd);
} else if (item.start?.date) {
// For full-day events, create date at noon local time to avoid timezone shifts
const [startYear, startMonth, startDay] = item.start.date.split('-').map(Number);
const startDate = new Date(startYear, startMonth - 1, startDay, 12, 0, 0);

// Check if it's a multi-day event
if (item.end?.date) {
const [endYear, endMonth, endDay] = item.end.date.split('-').map(Number);
const endDate = new Date(endYear, endMonth - 1, endDay, 12, 0, 0);

// Create an event for each day in the range
const currentDate = new Date(startDate);
while (currentDate < endDate) {
const eventCopy: Event = {
...toAdd,
id: `${item.id}-${currentDate.getFullYear()}-${currentDate.getMonth()}-${currentDate.getDate()}`,
start: new Date(currentDate),
isAllDay: true,
};
res.push(eventCopy);
currentDate.setDate(currentDate.getDate() + 1);
}
} else {
// Single-day event
toAdd.start = startDate;
toAdd.isAllDay = true;
res.push(toAdd);
}
}
} catch (err) {
console.warn(`Failed to parse event: ${item.id}`, err);
Expand Down
23 changes: 15 additions & 8 deletions src/components/Events/EventDays.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,21 @@ const EventComponent = ({ event, day, month }: Props) => {
<div className="space-y-1">
<h3 className="text-xs sm:text-sm md:text-base font-medium text-gray-200">Date & Time</h3>
<p className="text-xs sm:text-sm md:text-base text-gray-100">
{formatDate(localStartDate)}{" "}at{" "}
<span className="font-semibold">
{localStartDate.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
})}
</span>
{formatDate(localStartDate)}
{event.isAllDay ? (
<span className="font-semibold"> - All Day</span>
) : (
<>
{" "}at{" "}
<span className="font-semibold">
{localStartDate.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
})}
</span>
</>
)}
</p>
</div>
)}
Expand Down
21 changes: 14 additions & 7 deletions src/components/Events/EventTime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
type Props = {
dateString: string;
location?: string;
isAllDay?: boolean;
};

export default function UpcomingEventCard({ dateString, location }: Props) {
export default function UpcomingEventCard({ dateString, location, isAllDay }: Props) {
const date = new Date(dateString);
return (
<div className="text-black rounded-xl p-4">
Expand All @@ -26,12 +27,18 @@ export default function UpcomingEventCard({ dateString, location }: Props) {
month: 'long',
day: 'numeric',
})}{' '}
at{' '}
{date?.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: date.getMinutes() > 0 ? 'numeric' : undefined,
timeZoneName: 'short',
})}
{isAllDay ? (
<span className="font-semibold">- All Day</span>
) : (
<>
at{' '}
{date?.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: date.getMinutes() > 0 ? 'numeric' : undefined,
timeZoneName: 'short',
})}
</>
)}
</span>
</div>
</div>
Expand Down
25 changes: 16 additions & 9 deletions src/components/Events/UpcomingEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default function UpcomingEvents() {
<div className="rounded-lg p-3 md:p-4 backdrop-blur-sm backdrop-filter">
<h2 className="text-base font-bold lowercase text-gray-900 md:text-xl">{event.title}</h2>
{event.start && (
<EventTime dateString={event.start.toISOString()} location={event.location} />
<EventTime dateString={event.start.toISOString()} location={event.location} isAllDay={event.isAllDay} />
)}
</div>
</div>
Expand All @@ -109,14 +109,21 @@ export default function UpcomingEvents() {
<div className="space-y-1">
<h3 className="text-xs sm:text-sm md:text-base font-medium text-gray-200">Date & Time</h3>
<p className="text-xs sm:text-sm md:text-base text-gray-100">
{formatDate(event.start)}{" "}at{" "}
<span className="font-semibold">
{event.start.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
})}
</span>
{formatDate(event.start)}{' '}
{event.isAllDay ? (
<span className="font-semibold">- All Day</span>
) : (
<>
at{' '}
<span className="font-semibold">
{event.start.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
})}
</span>
</>
)}
</p>
</div>
)}
Expand Down