Skip to content

Commit

Permalink
Merge pull request #53 from atiqurx/main
Browse files Browse the repository at this point in the history
add calendar to the events page
  • Loading branch information
atiqurx authored Oct 2, 2024
2 parents b3b8aa2 + eec9280 commit ddc1024
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 93 deletions.
40 changes: 40 additions & 0 deletions src/app/api/events/byDate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NextResponse } from "next/server";
import { GET as getEvents } from "../route";
import type { Event } from "@/lib/types.d.ts";

const calendarId = process.env.GOOGLE_CALENDAR_ID;
const endpoint = `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`;

export async function GET(request: Request) {
const url = new URL(request.url);
const year = parseInt(url.searchParams.get("year") || "0", 10);
const month = parseInt(url.searchParams.get("month") || "0", 10);

// Fetch filtered events directly
const filteredEvents = await byDate(month, year);
return NextResponse.json(filteredEvents);
}

export async function byDate(month: number, year: number): Promise<Event[]> {
const response = await getEvents(); // Fetch the events data directly
const data = await response.json(); // Parse the response to JSON
const earliest = new Date(year, month, 1);
const latest = new Date(year, month + 1, 0);

// Filter and transform events in a single pass
const filteredEvents = data
.map((item: any) => {
const startDate = item.start ? new Date(item.start) : undefined; // Ensure start is defined
return {
...item,
start: startDate,
};
})
.filter((event: Event) => {
if (!event.start) return false; // Exclude events with no start date
const eventDate = new Date(event.start);
return eventDate >= earliest && eventDate <= latest;
});

return filteredEvents;
}
51 changes: 51 additions & 0 deletions src/app/api/events/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Event } from "@/lib/types.d.ts";
import { NextResponse } from "next/server";

export const dynamic = "force-dynamic";
const calendarId = process.env.GOOGLE_CALENDAR_ID;
const endpoint = `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`;

export async function GET() {
// Fetch and parse data
const response = await fetch(
`${endpoint}?key=${process.env.CALENDAR_API_KEY}`,
{ cache: "no-store" }
);
const data = await response.json();

const items = data.items as unknown[] | undefined;

if (!items || !Array.isArray(items) || items.length === 0) {
return new Response("No events found", { status: 404 });
}

// Use a single pass to parse and filter
const now = new Date();
now.setHours(0, 0, 0, 0);

const upcoming = items
.filter((item: unknown) => {
// Check for necessary properties
return (
item &&
typeof item === "object" &&
"id" in item &&
"summary" in item &&
"start" in item &&
typeof item.start === "object" &&
item.start !== null &&
"dateTime" in item.start &&
typeof item.start.dateTime === "string"
);
})
.map((item: any) => ({
id: item.id as string,
title: item.summary as string,
location: typeof item.location === "string" ? item.location : undefined,
start: new Date(item.start.dateTime),
}))
.filter((event: Event) => event.start !== undefined && event.start > now)
.sort((a, b) => a.start.getTime() - b.start.getTime());

return new NextResponse<Event[]>(JSON.stringify(upcoming));
}
19 changes: 19 additions & 0 deletions src/app/events/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Calendar from "@/components/events/Calendar";
type Params = {
month: string | null;
year: string | null;
};

export default function Events({ searchParams }: { searchParams: Params }) {
const today = new Date();
const month = parseInt(searchParams.month || today.getMonth().toString());
const year = parseInt(searchParams.year || today.getFullYear().toString());

return (
<div className="h-fit w-full bg-[url(/assets/apply/apply-bg.png)] bg-cover bg-center py-20">
<div className="flex p-10 md:p-4 justify-between pt-16 md:mx-20">
<Calendar month={month} year={year} />
</div>
</div>
);
}
181 changes: 88 additions & 93 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,97 @@
import React, { useState } from "react";

const Navbar = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);

const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen);
};
const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen);
};

// TODO: Replace "#" with actual links
const menuItems = [
{ name: "about", link: "/about" },
{ name: "officers", link: "/officers" },
{ name: "events", link: "#" },
{ name: "connect", link: "/connect" },
{
name: "apply",
link: "https://mavorgs.campuslabs.com/engage/organization/acm",
},
];
// TODO: Replace "#" with actual links
const menuItems = [
{ name: "about", link: "/about" },
{ name: "officers", link: "/officers" },
{ name: "events", link: "/events" },
{ name: "connect", link: "/connect" },
{
name: "apply",
link: "https://mavorgs.campuslabs.com/engage/organization/acm",
},
];

return (
<nav className="backdrop-blur-md bg-white/10 border-gray-200 sticky top-0 z-50">
<div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a
href="/"
className="flex items-center space-x-3 rtl:space-x-reverse"
>
<img
src="/assets/acm-logos/acm-logo.svg"
className="h-8"
alt="ACM UTA Logo"
/>
<span className="self-center text-2xl font-extrabold whitespace-nowrap">
acmuta
</span>
</a>
<button
type="button"
className="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-white rounded-lg md:hidden"
aria-controls="navbar-default"
aria-expanded={isMenuOpen}
onClick={toggleMenu}
>
<span className="sr-only">Open main menu</span>
<svg
className="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 17 14"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1 1h15M1 7h15M1 13h15"
/>
</svg>
</button>
<div
className={`${
isMenuOpen ? "block" : "hidden"
} w-full md:block md:w-auto`}
id="navbar-default"
>
<ul className="font-bold flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0">
{menuItems.map((item, index) => (
<li key={index}>
<a
href={item.link}
className={`block text-white rounded px-2 transition-transform duration-300 ease-in-out hover:shadow-inner hover:translate-y-1 hover:-translate-x-1 ${
item.name === "apply"
? "md:border md:border-white/60 md:rounded-2xl md:px-2 py-0"
: ""
} lg:hover:bg-transparent sm:hover:bg-white/10 md:text-white-700 border border-transparent hover:border-white/60 hover:rounded-2xl`}
aria-current={
item.name === "Home" ? "page" : undefined
}
style={{ boxShadow: "inset 0 0 0 rgba(0, 0, 0, 0)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.boxShadow =
"inset 0 2px 4px rgba(0, 0, 0, 0.6)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.boxShadow =
"inset 0 0 0 rgba(0, 0, 0, 0)")
}
>
{item.name}
</a>
</li>
))}
</ul>
</div>
</div>
</nav>
);
return (
<nav className="backdrop-blur-md bg-white/10 border-gray-200 sticky top-0 z-50">
<div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="/" className="flex items-center space-x-3 rtl:space-x-reverse">
<img
src="/assets/acm-logos/acm-logo.svg"
className="h-8"
alt="ACM UTA Logo"
/>
<span className="self-center text-2xl font-extrabold whitespace-nowrap">
acmuta
</span>
</a>
<button
type="button"
className="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-white rounded-lg md:hidden"
aria-controls="navbar-default"
aria-expanded={isMenuOpen}
onClick={toggleMenu}
>
<span className="sr-only">Open main menu</span>
<svg
className="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 17 14"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1 1h15M1 7h15M1 13h15"
/>
</svg>
</button>
<div
className={`${
isMenuOpen ? "block" : "hidden"
} w-full md:block md:w-auto`}
id="navbar-default"
>
<ul className="font-bold flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0">
{menuItems.map((item, index) => (
<li key={index}>
<a
href={item.link}
className={`block text-white rounded px-2 transition-transform duration-300 ease-in-out hover:shadow-inner hover:translate-y-1 hover:-translate-x-1 ${
item.name === "apply"
? "md:border md:border-white/60 md:rounded-2xl md:px-2 py-0"
: ""
} lg:hover:bg-transparent sm:hover:bg-white/10 md:text-white-700 border border-transparent hover:border-white/60 hover:rounded-2xl`}
aria-current={item.name === "Home" ? "page" : undefined}
style={{ boxShadow: "inset 0 0 0 rgba(0, 0, 0, 0)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.boxShadow =
"inset 0 2px 4px rgba(0, 0, 0, 0.6)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.boxShadow =
"inset 0 0 0 rgba(0, 0, 0, 0)")
}
>
{item.name}
</a>
</li>
))}
</ul>
</div>
</div>
</nav>
);
};

export default Navbar;
68 changes: 68 additions & 0 deletions src/components/events/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Link from "next/link";
import CalendarBody from "./CalendarBody";
import { byDate } from "@/app/api/events/byDate/route";

type Props = {
month: number;
year: number;
};

const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];

function changeMonth(
amt: "inc" | "dec",
{ month, year }: { month: number; year: number }
) {
if (amt === "inc") {
if (month === 11) return { month: 0, year: year + 1 };
return { month: month + 1, year };
}
if (month === 0) return { month: 11, year: year - 1 };
return { month: month - 1, year };
}

async function Calendar({ month, year }: Props) {
const events = await byDate(month, year);
const nextParams = changeMonth("inc", { month, year });
const prevParams = changeMonth("dec", { month, year });

return (
<div className="w-full rounded-3xl border border-[#ffffff82] bg-gradient-to-tr from-[#ffffff1f] from-[3.07%] to-[#ffffff08] to-[96.39%] p-5 shadow-md backdrop-blur-xl">
<div className="mb-5 flex items-center justify-between text-white flex-wrap">
<Link
className="w-10 text-lg md:text-xl"
href={{
pathname: "/events",
query: { month: prevParams.month, year: prevParams.year },
}}
>{`<`}</Link>
<span className="w-32 text-center text-xl font-semibold md:text-2xl">
{monthNames[month]}
</span>
<Link
className="w-10 text-lg md:text-xl"
href={{
pathname: "/events",
query: { month: nextParams.month, year: nextParams.year },
}}
>{`>`}</Link>
</div>
<CalendarBody month={month} year={year} events={events} />
</div>
);
}

export default Calendar;
Loading

0 comments on commit ddc1024

Please sign in to comment.