Skip to content

Commit 294f115

Browse files
authored
Merge pull request #3830 from Voyager-Ship/voyager-ship/events-tweaks
New Events: Fix events section
2 parents 44bb290 + 45d9ff4 commit 294f115

File tree

22 files changed

+1277
-332
lines changed

22 files changed

+1277
-332
lines changed

app/(home)/events/[id]/page.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
} from "@/server/services/hackathons";
77
import { getRegisterForm } from "@/server/services/registerForms";
88
import { getAuthSession } from "@/lib/auth/authSession";
9-
import HackathonEventLayout from "@/components/hackathons/event-layouts/HackathonEventLayout";
10-
import WorkshopBootcampEventLayout from "@/components/hackathons/event-layouts/WorkshopBootcampEventLayout";
9+
import LegacyEventLayout from "@/components/hackathons/event-layouts/LegacyEventLayout";
10+
import ModernEventLayout from "@/components/hackathons/event-layouts/ModernEventLayout";
1111
import { createMetadata } from "@/utils/metadata";
1212
import type { Metadata } from "next";
1313

@@ -83,16 +83,12 @@ export default async function HackathonPage({
8383

8484
if (!hackathon) redirect("/hackathons");
8585

86-
// Determine event type - default to "hackathon" for backwards compatibility
87-
const eventType = hackathon.event || "hackathon";
88-
const isHackathon = eventType === "hackathon";
89-
const isWorkshopOrBootcamp =
90-
eventType === "workshop" || eventType === "bootcamp";
86+
// Layout depends only on new_layout; when null/undefined, use legacy
87+
const useModernLayout = hackathon.new_layout === true;
9188

92-
// Render appropriate layout based on event type
93-
if (isWorkshopOrBootcamp) {
89+
if (useModernLayout) {
9490
return (
95-
<WorkshopBootcampEventLayout
91+
<ModernEventLayout
9692
hackathon={hackathon}
9793
id={id}
9894
isRegistered={isRegistered}
@@ -101,9 +97,8 @@ export default async function HackathonPage({
10197
);
10298
}
10399

104-
// Default to hackathon layout (for hackathon type or any other/unknown types)
105100
return (
106-
<HackathonEventLayout
101+
<LegacyEventLayout
107102
hackathon={hackathon}
108103
id={id}
109104
isRegistered={isRegistered}

app/(home)/hackathons/[id]/page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,13 @@ export default async function HackathonPage({
152152
{hackathon.content.tracks_text && <About hackathon={hackathon} />}
153153
{hackathon.content.tracks && <Tracks hackathon={hackathon} />}
154154
<Resources hackathon={hackathon} />
155-
{hackathon.content.schedule && <Schedule hackathon={hackathon} />}
155+
<Schedule
156+
hackathon={hackathon}
157+
scheduleSource={hackathon.google_calendar_id ? "google-calendar" : "database"}
158+
googleCalendarConfig={hackathon.google_calendar_id ? {
159+
calendarId: hackathon.google_calendar_id,
160+
} : undefined}
161+
/>
156162
<Submission hackathon={hackathon} />
157163
{hackathon.content.speakers && hackathon.content.speakers.length > 0 && (
158164
<MentorsJudges hackathon={hackathon} />

app/api/calendar/google/route.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { transformGoogleEventsToSchedule, GoogleCalendarEvent } from '@/lib/hackathons/schedule-strategy';
3+
4+
/**
5+
* Google Calendar API route
6+
*
7+
* Fetches events from a PUBLIC Google Calendar using an API Key.
8+
* Requires GOOGLE_CALENDAR_API_KEY environment variable.
9+
*
10+
* Query parameters:
11+
* - calendarId: The Google Calendar ID (required)
12+
* - maxResults: Maximum number of events to fetch (default: 100)
13+
* - timeMin: Only fetch events after this ISO date
14+
* - timeMax: Only fetch events before this ISO date
15+
*/
16+
17+
export async function GET(request: NextRequest) {
18+
try {
19+
const apiKey = process.env.NEXT_PUBLIC_GOOGLE_CALENDAR_API_KEY;
20+
21+
if (!apiKey) {
22+
return NextResponse.json(
23+
{ error: 'NEXT_PUBLIC_GOOGLE_CALENDAR_API_KEY not configured' },
24+
{ status: 500 }
25+
);
26+
}
27+
28+
const { searchParams } = new URL(request.url);
29+
30+
const calendarId = searchParams.get('calendarId');
31+
const maxResults = searchParams.get('maxResults') || '100';
32+
const timeMin = searchParams.get('timeMin');
33+
const timeMax = searchParams.get('timeMax');
34+
35+
if (!calendarId) {
36+
return NextResponse.json(
37+
{ error: 'calendarId is required' },
38+
{ status: 400 }
39+
);
40+
}
41+
42+
// Fetch all events using pagination
43+
const allEvents: GoogleCalendarEvent[] = [];
44+
let pageToken: string | undefined = undefined;
45+
46+
do {
47+
// Build Google Calendar API URL
48+
const apiParams = new URLSearchParams({
49+
key: apiKey,
50+
maxResults: '250', // Max allowed per request
51+
singleEvents: 'true',
52+
orderBy: 'startTime',
53+
// Include conference data (Google Meet links, etc.)
54+
conferenceDataVersion: '1',
55+
});
56+
57+
if (timeMin) {
58+
apiParams.set('timeMin', timeMin);
59+
}
60+
if (timeMax) {
61+
apiParams.set('timeMax', timeMax);
62+
}
63+
if (pageToken) {
64+
apiParams.set('pageToken', pageToken);
65+
}
66+
67+
const calendarUrl = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events?${apiParams.toString()}`;
68+
69+
const response = await fetch(calendarUrl, {
70+
next: { revalidate: 60 }, // Cache for 1 minute (reduced for faster updates)
71+
});
72+
73+
if (!response.ok) {
74+
const error = await response.text();
75+
console.error('Google Calendar API error:', error);
76+
77+
if (response.status === 404) {
78+
return NextResponse.json(
79+
{ error: 'Calendar not found. Make sure the calendar is public and the ID is correct.' },
80+
{ status: 404 }
81+
);
82+
}
83+
84+
return NextResponse.json(
85+
{ error: `Google Calendar API error: ${response.status}` },
86+
{ status: response.status }
87+
);
88+
}
89+
90+
const data = await response.json();
91+
const events = (data.items || []) as GoogleCalendarEvent[];
92+
allEvents.push(...events);
93+
94+
// Get next page token for pagination
95+
pageToken = data.nextPageToken;
96+
} while (pageToken);
97+
98+
const events = allEvents;
99+
100+
// Transform to ScheduleActivity format
101+
const schedule = transformGoogleEventsToSchedule(events);
102+
103+
// Get calendar timezone from first event or calendar metadata
104+
const calendarTimeZone = events[0]?.start?.timeZone || '';
105+
106+
return NextResponse.json({
107+
schedule,
108+
totalEvents: events.length,
109+
calendarId,
110+
timeZone: calendarTimeZone,
111+
});
112+
} catch (error) {
113+
console.error('Google Calendar API error:', error);
114+
115+
return NextResponse.json(
116+
{ error: error instanceof Error ? error.message : 'Failed to fetch calendar events' },
117+
{ status: 500 }
118+
);
119+
}
120+
}

app/hackathons/edit/initials.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export interface IDataMain {
7676
custom_link: string | null;
7777
top_most: boolean;
7878
event: string;
79+
new_layout: boolean;
80+
google_calendar_id: string | null;
7981
}
8082

8183
export const initialData = {
@@ -143,5 +145,7 @@ export interface IDataMain {
143145
custom_link: null,
144146
top_most: false,
145147
event: 'hackathon',
148+
new_layout: false,
149+
google_calendar_id: null,
146150
}
147151
}

0 commit comments

Comments
 (0)