|
1 | 1 | "use client";
|
2 |
| -import React from "react"; |
| 2 | +import React, { useEffect, useState } from "react"; |
3 | 3 | import Link from "next/link";
|
4 | 4 | import {
|
5 | 5 | Card,
|
|
9 | 9 | CardContent,
|
10 | 10 | CardFooter,
|
11 | 11 | } from "@/components/ui/card";
|
| 12 | + |
| 13 | +import axios from "axios"; |
| 14 | + |
12 | 15 | // import { HeroParallax } from "./components/hero-parallax"
|
13 | 16 |
|
14 | 17 | import Image from "next/image";
|
@@ -109,7 +112,69 @@ import Image from "next/image";
|
109 | 112 | // },
|
110 | 113 | // ];
|
111 | 114 |
|
| 115 | +interface Event { |
| 116 | + id: number; |
| 117 | + name: string; |
| 118 | + description: string; |
| 119 | + date: string; |
| 120 | + time: string; |
| 121 | + mode: string; |
| 122 | +} |
| 123 | + |
112 | 124 | export default function Home() {
|
| 125 | + type EventCategory = "upcoming" | "past" | "ongoing"; |
| 126 | + const [events, setEvents] = useState<{ |
| 127 | + upcoming: Event[]; |
| 128 | + past: Event[]; |
| 129 | + ongoing: Event[]; |
| 130 | + }>({ |
| 131 | + upcoming: [], |
| 132 | + past: [], |
| 133 | + ongoing: [], |
| 134 | + }); |
| 135 | + const [displayedEvents, setDisplayedEvents] = useState<Event[]>([]); |
| 136 | + const [selectedCategory, setSelectedCategory] = |
| 137 | + useState<EventCategory>("upcoming"); |
| 138 | + |
| 139 | + // Fetch events once on component mount |
| 140 | + useEffect(() => { |
| 141 | + async function fetchEvents() { |
| 142 | + try { |
| 143 | + const [upcomingRes, pastRes, ongoingRes] = await Promise.all([ |
| 144 | + axios.get("/api/v1/get/events?category=upcoming"), |
| 145 | + axios.get("/api/v1/get/events?category=past"), |
| 146 | + axios.get("/api/v1/get/events?category=ongoing"), |
| 147 | + ]); |
| 148 | + |
| 149 | + setEvents({ |
| 150 | + upcoming: upcomingRes.data.slice(0, 3), |
| 151 | + past: pastRes.data.slice(0, 3), |
| 152 | + ongoing: ongoingRes.data.slice(0, 3), |
| 153 | + }); |
| 154 | + |
| 155 | + // Default displayed events to "upcoming" |
| 156 | + setDisplayedEvents(upcomingRes.data.slice(0, 3)); |
| 157 | + } catch (error) { |
| 158 | + console.error("Error fetching events:", error); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + fetchEvents(); |
| 163 | + }, []); |
| 164 | + |
| 165 | + // Handle category toggle |
| 166 | + const handleCategoryChange = (category: EventCategory) => { |
| 167 | + setSelectedCategory(category); |
| 168 | + setDisplayedEvents(events[category]); |
| 169 | + }; |
| 170 | + |
| 171 | + // Category titles for dynamic title display |
| 172 | + const categoryTitles: { [key in EventCategory]: string } = { |
| 173 | + upcoming: "Upcoming ", |
| 174 | + past: "Past ", |
| 175 | + ongoing: "Ongoing ", |
| 176 | + }; |
| 177 | + |
113 | 178 | return (
|
114 | 179 | <div className="flex flex-col min-h-dvh">
|
115 | 180 | {/* <HeroParallax products={products} /> */}
|
@@ -177,132 +242,76 @@ export default function Home() {
|
177 | 242 | </div>
|
178 | 243 | </div>
|
179 | 244 | </section>
|
180 |
| - <section className="w-full py-12 md:py-24 lg:py-32 flex item-center justify-center bg-secondary"> |
| 245 | + <section className="w-full py-12 md:py-24 lg:py-24 flex item-center justify-center bg-secondary"> |
181 | 246 | <div className="container px-4 md:px-6 space-y-6">
|
182 | 247 | <div className="flex flex-col items-center justify-center space-y-4 text-center">
|
183 |
| - <div className="space-y-2"> |
184 |
| - <h2 className="text-3xl font-bold tracking-tighter sm:text-5xl"> |
185 |
| - Upcoming <span className="text-primary">Events</span> |
186 |
| - </h2> |
187 |
| - <p className="max-w-[900px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed"> |
188 |
| - Check out our upcoming events and workshops to learn new skills, |
189 |
| - network with fellow coders, and have fun! |
190 |
| - </p> |
| 248 | + <h2 className="text-3xl font-bold tracking-tighter sm:text-5xl"> |
| 249 | + {categoryTitles[selectedCategory]} |
| 250 | + <span className="text-primary">Events</span> |
| 251 | + </h2> |
| 252 | + <div className="flex gap-4"> |
| 253 | + {(["upcoming", "ongoing", "past"] as EventCategory[]).map( |
| 254 | + (category) => ( |
| 255 | + <button |
| 256 | + key={category} |
| 257 | + onClick={() => handleCategoryChange(category)} |
| 258 | + className={` px-2 py-1 md:px-4 md:py-2 rounded-md ${ |
| 259 | + selectedCategory === category |
| 260 | + ? "bg-primary text-secondary text-sm sm:text-base" |
| 261 | + : "bg-secondary text-sm sm:text-base border hover:bg-muted hover:text-secondary transition duration-250" |
| 262 | + }`} |
| 263 | + > |
| 264 | + {category.charAt(0).toUpperCase() + category.slice(1)} |
| 265 | + </button> |
| 266 | + ), |
| 267 | + )} |
191 | 268 | </div>
|
| 269 | + <p className="max-w-[900px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed"> |
| 270 | + Check out our events and workshops to learn new skills, network |
| 271 | + with fellow coders, and have fun! |
| 272 | + </p> |
192 | 273 | </div>
|
193 |
| - <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> |
194 |
| - <Card className="flex flex-col"> |
195 |
| - <CardHeader> |
196 |
| - <CardTitle>Intro to React Workshop</CardTitle> |
197 |
| - <CardDescription> |
198 |
| - Learn the fundamentals of React.js in this hands-on workshop. |
199 |
| - </CardDescription> |
200 |
| - </CardHeader> |
201 |
| - <CardContent className="flex-1"> |
202 |
| - <div className="grid gap-2"> |
203 |
| - <div className="flex items-center gap-2"> |
204 |
| - <CalendarIcon className="h-5 w-5 text-muted-foreground" /> |
205 |
| - <p className="text-sm text-muted-foreground"> |
206 |
| - June 15, 2024 |
207 |
| - </p> |
208 |
| - </div> |
209 |
| - <div className="flex items-center gap-2"> |
210 |
| - <ClockIcon className="h-5 w-5 text-muted-foreground" /> |
211 |
| - <p className="text-sm text-muted-foreground"> |
212 |
| - 6:00 PM - 8:00 PM |
213 |
| - </p> |
214 |
| - </div> |
215 |
| - <div className="flex items-center gap-2"> |
216 |
| - <LocateIcon className="h-5 w-5 text-muted-foreground" /> |
217 |
| - <p className="text-sm text-muted-foreground">Online</p> |
218 |
| - </div> |
219 |
| - </div> |
220 |
| - </CardContent> |
221 |
| - <CardFooter> |
222 |
| - <Link |
223 |
| - href="#" |
224 |
| - className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" |
225 |
| - prefetch={false} |
226 |
| - > |
227 |
| - Register |
228 |
| - </Link> |
229 |
| - </CardFooter> |
230 |
| - </Card> |
231 |
| - <Card className="flex flex-col"> |
232 |
| - <CardHeader> |
233 |
| - <CardTitle>Hackathon: Build a Web App</CardTitle> |
234 |
| - <CardDescription> |
235 |
| - Join our 24-hour hackathon and build a web application from |
236 |
| - scratch. |
237 |
| - </CardDescription> |
238 |
| - </CardHeader> |
239 |
| - <CardContent className="flex-1"> |
240 |
| - <div className="grid gap-2"> |
241 |
| - <div className="flex items-center gap-2"> |
242 |
| - <CalendarIcon className="h-5 w-5 text-muted-foreground" /> |
243 |
| - <p className="text-sm text-muted-foreground"> |
244 |
| - July 20-21, 2024 |
245 |
| - </p> |
246 |
| - </div> |
247 |
| - <div className="flex items-center gap-2"> |
248 |
| - <ClockIcon className="h-5 w-5 text-muted-foreground" /> |
249 |
| - <p className="text-sm text-muted-foreground"> |
250 |
| - 9:00 AM - 9:00 AM |
251 |
| - </p> |
252 |
| - </div> |
253 |
| - <div className="flex items-center gap-2"> |
254 |
| - <LocateIcon className="h-5 w-5 text-muted-foreground" /> |
255 |
| - <p className="text-sm text-muted-foreground">Online</p> |
256 |
| - </div> |
257 |
| - </div> |
258 |
| - </CardContent> |
259 |
| - <CardFooter> |
260 |
| - <Link |
261 |
| - href="#" |
262 |
| - className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" |
263 |
| - prefetch={false} |
264 |
| - > |
265 |
| - Register |
266 |
| - </Link> |
267 |
| - </CardFooter> |
268 |
| - </Card> |
269 |
| - <Card className="flex flex-col"> |
270 |
| - <CardHeader> |
271 |
| - <CardTitle>Intro to Data Structures</CardTitle> |
272 |
| - <CardDescription> |
273 |
| - Dive into the fundamentals of data structures and algorithms. |
274 |
| - </CardDescription> |
275 |
| - </CardHeader> |
276 |
| - <CardContent className="flex-1"> |
277 |
| - <div className="grid gap-2"> |
278 |
| - <div className="flex items-center gap-2"> |
279 |
| - <CalendarIcon className="h-5 w-5 text-muted-foreground" /> |
280 |
| - <p className="text-sm text-muted-foreground"> |
281 |
| - August 5, 2024 |
282 |
| - </p> |
283 |
| - </div> |
284 |
| - <div className="flex items-center gap-2"> |
285 |
| - <ClockIcon className="h-5 w-5 text-muted-foreground" /> |
286 |
| - <p className="text-sm text-muted-foreground"> |
287 |
| - 7:00 PM - 9:00 PM |
288 |
| - </p> |
289 |
| - </div> |
290 |
| - <div className="flex items-center gap-2"> |
291 |
| - <LocateIcon className="h-5 w-5 text-muted-foreground" /> |
292 |
| - <p className="text-sm text-muted-foreground">Online</p> |
293 |
| - </div> |
294 |
| - </div> |
295 |
| - </CardContent> |
296 |
| - <CardFooter> |
297 |
| - <Link |
298 |
| - href="#" |
299 |
| - className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" |
300 |
| - prefetch={false} |
301 |
| - > |
302 |
| - Register |
303 |
| - </Link> |
304 |
| - </CardFooter> |
305 |
| - </Card> |
| 274 | + |
| 275 | + <div className="lg:flex grid gap-6 lg:justify-center sm:grid-cols-2 lg:grid-cols-3"> |
| 276 | + {displayedEvents.length > 0 ? ( |
| 277 | + displayedEvents.map((event) => ( |
| 278 | + <Card key={event.id} className="lg:w-1/3 w-full flex flex-col"> |
| 279 | + <CardHeader> |
| 280 | + <CardTitle>{event.name}</CardTitle> |
| 281 | + <CardDescription>{event.description}</CardDescription> |
| 282 | + </CardHeader> |
| 283 | + <CardContent className="flex-1"> |
| 284 | + <div className="grid gap-2"> |
| 285 | + <div className="flex items-center gap-2"> |
| 286 | + <CalendarIcon className="h-5 w-5 text-muted-foreground" /> |
| 287 | + <p className="text-sm text-muted-foreground">{formatDate(event.date)}</p> |
| 288 | + </div> |
| 289 | + <div className="flex items-center gap-2"> |
| 290 | + <ClockIcon className="h-5 w-5 text-muted-foreground" /> |
| 291 | + <p className="text-sm text-muted-foreground">{formatTime(event.time)}</p> |
| 292 | + </div> |
| 293 | + <div className="flex items-center gap-2"> |
| 294 | + <LocateIcon className="h-5 w-5 text-muted-foreground" /> |
| 295 | + <p className="text-sm text-muted-foreground">{event.mode?"Online":"Offline"}</p> |
| 296 | + </div> |
| 297 | + </div> |
| 298 | + </CardContent> |
| 299 | + <CardFooter> |
| 300 | + <Link |
| 301 | + href={`/event/${event.id}`} |
| 302 | + className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" |
| 303 | + prefetch={false} |
| 304 | + > |
| 305 | + Register |
| 306 | + </Link> |
| 307 | + </CardFooter> |
| 308 | + </Card> |
| 309 | + )) |
| 310 | + ) : ( |
| 311 | + <div className="w-full col-span-5 content-center justify-center flex py-20 lg:pt-16 lg:pb-44"> |
| 312 | + <p className="text-center sm:text-xl">No <span className="text-primary">{selectedCategory}</span> events available. Check back soon for future updates.</p> |
| 313 | + </div> |
| 314 | + )} |
306 | 315 | </div>
|
307 | 316 | </div>
|
308 | 317 | </section>
|
@@ -474,6 +483,35 @@ export default function Home() {
|
474 | 483 | );
|
475 | 484 | }
|
476 | 485 |
|
| 486 | +function formatDate(dateString: string): string { |
| 487 | + const [year, month, day] = dateString.split("-"); |
| 488 | + const date = new Date(Number(year), Number(month) - 1, Number(day)); // Month is 0-indexed |
| 489 | + |
| 490 | + const formattedDay = date.getDate(); |
| 491 | + const monthName = date.toLocaleString("default", { month: "long" }); |
| 492 | + const formattedYear = date.getFullYear(); |
| 493 | + |
| 494 | + return `${formattedDay} ${monthName} ${formattedYear}`; |
| 495 | +} |
| 496 | + |
| 497 | +const formatTime = (timeString: string): string => { |
| 498 | + const timeRegex = /^(\d{2}):(\d{2}):(\d{2})$/; |
| 499 | + const match = timeString.match(timeRegex); |
| 500 | + if (!match) { |
| 501 | + return 'Invalid Time Format'; |
| 502 | + } |
| 503 | + |
| 504 | + let hours = parseInt(match[1], 10); |
| 505 | + const minutes = parseInt(match[2], 10); |
| 506 | + const ampm = hours >= 12 ? 'PM' : 'AM'; |
| 507 | + hours = hours % 12; |
| 508 | + hours = hours ? hours : 12; // Handle midnight (0 becomes 12) |
| 509 | + const formattedMinutes = String(minutes).padStart(2, '0'); |
| 510 | + |
| 511 | + return `${hours}:${formattedMinutes} ${ampm}`; |
| 512 | +}; |
| 513 | + |
| 514 | + |
477 | 515 | function LaptopIcon(
|
478 | 516 | props: React.JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>,
|
479 | 517 | ) {
|
|
0 commit comments