Skip to content

Commit e0cf3ab

Browse files
committed
Merge remote-tracking branch 'origin' into feat/gas-treemap
2 parents a9b23d3 + 294f115 commit e0cf3ab

File tree

314 files changed

+5097
-3148
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

314 files changed

+5097
-3148
lines changed

app/(home)/build-games/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import clsx from "clsx";
22
import Link from "next/link";
33
import "./styles.css";
44
import ReferralLink from "@/components/build-games/ReferralLink";
5-
import ApplicationStatusTracker from "@/components/build-games/ApplicationStatusTracker";
65
import ProgramTimelineWrapper from "@/components/build-games/ProgramTimelineWrapper";
76
import BuildGamesResourcesWrapper from "@/components/build-games/BuildGamesResourcesWrapper";
87
import HowItWorksWrapper from "@/components/build-games/HowItWorksWrapper";
@@ -1025,7 +1024,6 @@ function MainContent() {
10251024
<div className="absolute content-stretch flex flex-col items-start left-[-0.05px] right-[0.05px] top-0" data-name="Main">
10261025
<HeroSection />
10271026
<HeroTilesSection />
1028-
<ApplicationStatusTracker />
10291027
<div className="relative shrink-0 w-full" data-name="CTA">
10301028
<div className="flex flex-col justify-center size-full">
10311029
<div className="content-stretch flex flex-col gap-[16px] items-start justify-center px-[186px] py-0 relative w-full">

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/build-games/status/route.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export async function GET() {
1515
// Check all three participation paths in parallel:
1616
// 1. BuildGames application form
1717
// 2. RegisterForm for the Build Games hackathon
18-
// 3. Confirmed project member of a Build Games project
19-
const [application, registration, project] = await Promise.all([
18+
// 3. Project member (non-Removed) of a Build Games project
19+
const [application, registration, projects] = await Promise.all([
2020
prisma.buildGamesApplication.findUnique({
2121
where: { email: session.user.email },
2222
select: { id: true, first_name: true, project_name: true, created_at: true },
@@ -25,36 +25,78 @@ export async function GET() {
2525
where: { hackathon_id: BG_HACKATHON_ID, email: session.user.email },
2626
select: { id: true, name: true, created_at: true },
2727
}),
28-
prisma.project.findFirst({
28+
prisma.project.findMany({
2929
where: {
3030
hackaton_id: BG_HACKATHON_ID,
31-
members: { some: { email: session.user.email } },
31+
members: { some: { email: session.user.email, status: { not: "Removed" } } },
32+
},
33+
select: {
34+
id: true,
35+
project_name: true,
36+
created_at: true,
37+
members: {
38+
where: { email: session.user.email, status: { not: "Removed" } },
39+
select: { status: true },
40+
},
3241
},
33-
select: { id: true, project_name: true, created_at: true },
3442
}),
3543
]);
3644

37-
const isParticipant = !!(application || registration || project);
45+
const isParticipant = !!(application || registration || projects.length > 0);
3846

3947
if (!isParticipant) {
4048
return NextResponse.json({ isParticipant: false });
4149
}
4250

43-
// Use the best available project name: application > project > registration name
44-
const projectName =
45-
application?.project_name ??
46-
project?.project_name ??
47-
"Build Games 2026";
51+
// Fetch FormData for all projects in parallel
52+
const projectsWithResults = await Promise.all(
53+
projects.map(async (p) => {
54+
const formData = await prisma.formData.findFirst({
55+
where: { project_id: p.id },
56+
select: { form_data: true },
57+
});
58+
const buildGames = (formData?.form_data as Record<string, any>)?.build_games;
59+
const stage1Result: string | null = buildGames?.stage1_result ?? null;
60+
const isConfirmed = p.members.some((m) => m.status === "Confirmed");
61+
return { projectName: p.project_name, stage1Result, isConfirmed, createdAt: p.created_at };
62+
})
63+
);
64+
65+
// Selection logic:
66+
// 1. Prefer accepted projects; among those prefer confirmed membership.
67+
// 2. If multiple confirmed+accepted exist, show all of them.
68+
// 3. If no accepted projects, show the confirmed one.
69+
// 4. If only one project total, show it regardless.
70+
let selectedProjects = projectsWithResults;
71+
72+
if (projectsWithResults.length > 1) {
73+
const accepted = projectsWithResults.filter((p) => p.stage1Result === "accepted");
74+
if (accepted.length > 0) {
75+
const confirmedAccepted = accepted.filter((p) => p.isConfirmed);
76+
selectedProjects = confirmedAccepted.length > 0 ? confirmedAccepted : accepted;
77+
} else {
78+
const confirmed = projectsWithResults.filter((p) => p.isConfirmed);
79+
selectedProjects = confirmed.length > 0 ? [confirmed[0]] : [projectsWithResults[0]];
80+
}
81+
}
82+
83+
const stageResults = selectedProjects
84+
.filter((p) => p.stage1Result !== null)
85+
.map((p) => ({ projectName: p.projectName, stage1Result: p.stage1Result as string }));
86+
87+
const firstProject = selectedProjects[0];
88+
const projectName = firstProject?.projectName ?? application?.project_name ?? "Build Games 2026";
4889

4990
const createdAt = (
5091
application?.created_at ??
5192
registration?.created_at ??
52-
project?.created_at
93+
firstProject?.createdAt
5394
)?.toISOString() ?? new Date().toISOString();
5495

5596
return NextResponse.json({
5697
isParticipant: true,
5798
participant: { projectName, createdAt },
99+
stageResults,
58100
});
59101
} catch (error) {
60102
console.error('Error checking application status:', error);

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+
}

0 commit comments

Comments
 (0)