From e6d6dc16a62f8b4c52fc9f0c947ca3055f7356e9 Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 01:00:46 -0800 Subject: [PATCH 1/7] added iframe for OAH --- src/components/join/OAH.astro | 19 +++++++++++++++++++ src/components/join/OH.astro | 13 ------------- 2 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 src/components/join/OAH.astro delete mode 100644 src/components/join/OH.astro diff --git a/src/components/join/OAH.astro b/src/components/join/OAH.astro new file mode 100644 index 0000000..bf86022 --- /dev/null +++ b/src/components/join/OAH.astro @@ -0,0 +1,19 @@ +--- + +--- + +
+

Each officer has their own OAH

+

Check out our calendar!

+ +
diff --git a/src/components/join/OH.astro b/src/components/join/OH.astro deleted file mode 100644 index fcd3f36..0000000 --- a/src/components/join/OH.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -const {title, subtitle, image} = Astro.props; ---- - -
-

- {subtitle} -

-

- {title} -

- Office hours -
\ No newline at end of file From 8985c1eab4230a3cd7772d037dfc76c75192bda7 Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 01:01:01 -0800 Subject: [PATCH 2/7] added iframe for location --- src/components/join/BuildingLocation.astro | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/components/join/BuildingLocation.astro diff --git a/src/components/join/BuildingLocation.astro b/src/components/join/BuildingLocation.astro new file mode 100644 index 0000000..1d69b1e --- /dev/null +++ b/src/components/join/BuildingLocation.astro @@ -0,0 +1,20 @@ +--- + +--- + +
+

Each officer has their own OAH

+

Check out our calendar!

+ +
From 597cc76c995f7e340988596d5d27eda482c7ea5b Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 01:01:35 -0800 Subject: [PATCH 3/7] updated component imports --- src/components/join/Findus.astro | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/join/Findus.astro b/src/components/join/Findus.astro index 81bbd67..0b151c9 100644 --- a/src/components/join/Findus.astro +++ b/src/components/join/Findus.astro @@ -1,8 +1,10 @@ --- -import OH from "./OH.astro" +import OAH from "./OAH.astro"; +import BuildingLocation from "./BuildingLocation.astro"; ---
- - -
\ No newline at end of file + + + + From 3b69f744e8b99e2adb31e26c9503a1ec828768c1 Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 01:01:49 -0800 Subject: [PATCH 4/7] added upcoming events --- src/components/events/EventList.jsx | 170 +++++++++++++++++++++ src/components/events/UpcomingEvents.astro | 55 +++---- 2 files changed, 187 insertions(+), 38 deletions(-) create mode 100644 src/components/events/EventList.jsx diff --git a/src/components/events/EventList.jsx b/src/components/events/EventList.jsx new file mode 100644 index 0000000..bfce501 --- /dev/null +++ b/src/components/events/EventList.jsx @@ -0,0 +1,170 @@ +import React, { useEffect, useState } from "react"; + +const UpcomingEvent = ({ name, location, date, time, delay, description }) => ( +
+

+ {name} +

+
+

Location: {location}

+ {date && ( + <> +
+

{date}

+ + )} + {time && ( + <> +
+

{time}

+ + )} +
+

+ {description} +

+
+
+); + +const EventList = ({ CALENDAR_API_KEY }) => { + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const apiKey = CALENDAR_API_KEY; + const calendarId = "666sh64sku5n29qv2a2f4598jc@group.calendar.google.com"; + const userTimeZone = "America/Los_Angeles"; + + const loadGapiAndListEvents = async () => { + try { + console.log("Starting to load events..."); + + if (typeof window.gapi === "undefined") { + console.log("Loading GAPI script..."); + await new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = "https://apis.google.com/js/api.js"; + document.body.appendChild(script); + script.onload = () => { + console.log("GAPI script loaded"); + window.gapi.load("client", resolve); + }; + script.onerror = () => { + console.error("Failed to load GAPI script"); + reject(new Error("Failed to load the Google API script.")); + }; + }); + } + + console.log("Initializing GAPI client..."); + await window.gapi.client.init({ + apiKey: apiKey, + discoveryDocs: [ + "https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest", + ], + }); + + console.log("Fetching events..."); + const response = await window.gapi.client.calendar.events.list({ + calendarId: calendarId, + timeZone: userTimeZone, + singleEvents: true, + timeMin: new Date().toISOString(), + maxResults: 3, + orderBy: "startTime", + }); + + console.log("Response received:", response); + + if (response.result.items) { + setEvents(response.result.items); + } + } catch (error) { + console.error("Detailed Error: ", error); + setError(error.message || "Failed to load events"); + } finally { + setLoading(false); + } + }; + + if (!CALENDAR_API_KEY) { + setError("API key is missing"); + setLoading(false); + return; + } + + setLoading(true); + loadGapiAndListEvents(); + }, [CALENDAR_API_KEY]); + + if (!CALENDAR_API_KEY) { + return ( +
+ Error: Calendar API key is not configured +
+ ); + } + + return ( +
+ {error &&

Error: {error}

} + {!loading && !error && events.length === 0 && ( + + )} + {!loading && !error && events.length > 0 && ( +
+ {events.map((event, index) => { + const startDate = new Date( + event.start.dateTime || event.start.date, + ); + const day = startDate.toLocaleDateString("en-US", { + weekday: "short", + }); + const date = startDate.toLocaleDateString("en-US", { + day: "numeric", + month: "short", + year: "numeric", + }); + const time = startDate.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); + + return ( + + ); + })} +
+ )} +
+ ); +}; + +export default EventList; diff --git a/src/components/events/UpcomingEvents.astro b/src/components/events/UpcomingEvents.astro index 6b9cc3a..e1afe33 100644 --- a/src/components/events/UpcomingEvents.astro +++ b/src/components/events/UpcomingEvents.astro @@ -1,45 +1,24 @@ --- import UpcomingEvent from "./UpcomingEvent.astro"; import { LiaDotCircle } from "react-icons/lia"; +import EventList from "./EventList.jsx"; +const CALENDAR_API_KEY = import.meta.env.CALENDAR_API_KEY; --- -
-
-
-
- -

- Upcoming
Events -

-
-

- SCROLL DOWN TO SEE THE UPCOMING EVENTS FOR IEEE! + +

+
+
+
+ +

+ Upcoming
Events

-
- - - -
+

+ SCROLL DOWN TO SEE THE UPCOMING EVENTS FOR IEEE! +

- +
+ +
+
From 4fb8eb1959d208ed66456d5cfb01bab49a788819 Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 02:09:01 -0800 Subject: [PATCH 5/7] add support for ENV variables --- src/components/events/EventList.jsx | 4 ++-- src/components/events/UpcomingEvents.astro | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/events/EventList.jsx b/src/components/events/EventList.jsx index bfce501..2f8f0d1 100644 --- a/src/components/events/EventList.jsx +++ b/src/components/events/EventList.jsx @@ -36,14 +36,14 @@ const UpcomingEvent = ({ name, location, date, time, delay, description }) => (
); -const EventList = ({ CALENDAR_API_KEY }) => { +const EventList = ({ CALENDAR_API_KEY, EVENT_CALENDAR_ID }) => { const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const apiKey = CALENDAR_API_KEY; - const calendarId = "666sh64sku5n29qv2a2f4598jc@group.calendar.google.com"; + const calendarId = EVENT_CALENDAR_ID; const userTimeZone = "America/Los_Angeles"; const loadGapiAndListEvents = async () => { diff --git a/src/components/events/UpcomingEvents.astro b/src/components/events/UpcomingEvents.astro index e1afe33..e2bc77f 100644 --- a/src/components/events/UpcomingEvents.astro +++ b/src/components/events/UpcomingEvents.astro @@ -3,6 +3,7 @@ import UpcomingEvent from "./UpcomingEvent.astro"; import { LiaDotCircle } from "react-icons/lia"; import EventList from "./EventList.jsx"; const CALENDAR_API_KEY = import.meta.env.CALENDAR_API_KEY; +const EVENT_CALENDAR_ID = import.meta.env.EVENT_CALENDAR_ID; ---
@@ -19,6 +20,10 @@ const CALENDAR_API_KEY = import.meta.env.CALENDAR_API_KEY;

- +
From 786d6820e870d7a0bfe37da062842345775c7788 Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 02:09:15 -0800 Subject: [PATCH 6/7] added working calendar --- src/components/events/Calendar.jsx | 358 +++++++++++++++++++++++++++++ src/pages/events.astro | 10 +- 2 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 src/components/events/Calendar.jsx diff --git a/src/components/events/Calendar.jsx b/src/components/events/Calendar.jsx new file mode 100644 index 0000000..25c4245 --- /dev/null +++ b/src/components/events/Calendar.jsx @@ -0,0 +1,358 @@ +import React, { useState, useEffect } from "react"; + +const Calendar = ({ CALENDAR_API_KEY, EVENT_CALENDAR_ID }) => { + const [currentDate, setCurrentDate] = useState(new Date()); + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [hoveredEvent, setHoveredEvent] = useState(null); + const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); + + const getDaysInMonth = (date) => { + const year = date.getFullYear(); + const month = date.getMonth(); + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const daysInMonth = lastDay.getDate(); + const startingDay = firstDay.getDay(); + const endingDay = lastDay.getDay(); + + const days = []; + // Add empty slots for days before the first of the month + for (let i = 0; i < startingDay; i++) { + days.push(null); + } + // Add all days of the month + for (let i = 1; i <= daysInMonth; i++) { + days.push(new Date(year, month, i)); + } + // Add empty slots for remaining days in the last week + const remainingDays = 6 - endingDay; + for (let i = 0; i < remainingDays; i++) { + days.push(null); + } + return days; + }; + + // Format date to match event dates + const formatDate = (date) => { + if (!date) return ""; + return date.toISOString().split("T")[0]; + }; + + // Get events for a specific day + const getEventsForDay = (day) => { + if (!day) return []; + const dayStr = formatDate(day); + return events.filter((event) => { + const eventDate = event.start.dateTime + ? new Date(event.start.dateTime).toISOString().split("T")[0] + : event.start.date; + return eventDate === dayStr; + }); + }; + + // Format time for display + const formatEventTime = (dateTimeStr) => { + if (!dateTimeStr) return ""; + const date = new Date(dateTimeStr); + return date.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); + }; + + useEffect(() => { + const calendarId = EVENT_CALENDAR_ID; + const userTimeZone = "America/Los_Angeles"; + + const loadGapiAndListEvents = async () => { + try { + console.log("Starting to load events..."); + + if (typeof window.gapi === "undefined") { + console.log("Loading GAPI script..."); + await new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = "https://apis.google.com/js/api.js"; + document.body.appendChild(script); + script.onload = () => { + console.log("GAPI script loaded"); + window.gapi.load("client", resolve); + }; + script.onerror = () => { + console.error("Failed to load GAPI script"); + reject(new Error("Failed to load the Google API script.")); + }; + }); + } + + console.log("Initializing GAPI client..."); + await window.gapi.client.init({ + apiKey: CALENDAR_API_KEY, + discoveryDocs: [ + "https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest", + ], + }); + + // Get first and last day of current month + const firstDay = new Date( + currentDate.getFullYear(), + currentDate.getMonth(), + 1, + ); + const lastDay = new Date( + currentDate.getFullYear(), + currentDate.getMonth() + 1, + 0, + ); + + console.log("Fetching events..."); + const response = await window.gapi.client.calendar.events.list({ + calendarId: calendarId, + timeZone: userTimeZone, + singleEvents: true, + timeMin: firstDay.toISOString(), + timeMax: lastDay.toISOString(), + orderBy: "startTime", + }); + + console.log("Response received:", response); + + if (response.result.items) { + setEvents(response.result.items); + } + } catch (error) { + console.error("Detailed Error: ", error); + setError(error.message || "Failed to load events"); + } finally { + setLoading(false); + } + }; + + if (!CALENDAR_API_KEY) { + setError("API key is missing"); + setLoading(false); + return; + } + + setLoading(true); + loadGapiAndListEvents(); + }, [CALENDAR_API_KEY, currentDate]); + + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + + const changeMonth = (increment) => { + setCurrentDate( + new Date( + currentDate.getFullYear(), + currentDate.getMonth() + increment, + 1, + ), + ); + }; + + const handleEventMouseEnter = (event, e) => { + const target = e.target; + setTooltipPosition({ + x: e.clientX, + y: e.clientY, + }); + setHoveredEvent({ event, target }); + }; + + const handleEventMouseLeave = () => { + setHoveredEvent(null); + }; + + const handleMouseMove = (e) => { + if (hoveredEvent) { + // Check if the mouse is still over the event element + const rect = hoveredEvent.target.getBoundingClientRect(); + const isStillHovering = + e.clientX >= rect.left && + e.clientX <= rect.right && + e.clientY >= rect.top && + e.clientY <= rect.bottom; + + if (!isStillHovering) { + setHoveredEvent(null); + } else { + setTooltipPosition({ + x: e.clientX, + y: e.clientY, + }); + } + } + }; + + useEffect(() => { + const handleScroll = () => { + if (hoveredEvent) { + const rect = hoveredEvent.target.getBoundingClientRect(); + const mouseX = tooltipPosition.x - 15; // Subtract the offset added to the tooltip + const mouseY = tooltipPosition.y - 15; + + const isStillHovering = + mouseX >= rect.left && + mouseX <= rect.right && + mouseY >= rect.top && + mouseY <= rect.bottom; + + if (!isStillHovering) { + setHoveredEvent(null); + } + } + }; + + window.addEventListener("scroll", handleScroll, true); + return () => window.removeEventListener("scroll", handleScroll, true); + }, [hoveredEvent, tooltipPosition]); + + if (!CALENDAR_API_KEY) { + return ( +
+ Error: Calendar API key is not configured +
+ ); + } + + return ( +
+ {/* Hovering Calendar Header */} +
+
+
+ +

+ {monthNames[currentDate.getMonth()]} {currentDate.getFullYear()} +

+ +
+
+
+ + {/* Main Calendar Body */} +
+ {/* Week Days Header */} +
+ {weekDays.map((day, index) => ( +
+
+ {day} +
+
+ ))} +
+ + {/* Calendar Grid */} +
+ {getDaysInMonth(currentDate).map((day, index) => ( +
+ {day && ( + <> +
+ {day.getDate()} +
+
+ {getEventsForDay(day).map((event, eventIndex) => ( +
handleEventMouseEnter(event, e)} + onMouseLeave={handleEventMouseLeave} + > + {event.summary} +
+ ))} +
+ + )} +
+ ))} +
+ + {/* Tooltip */} + {hoveredEvent && ( +
+

+ {hoveredEvent.event.summary} +

+ {hoveredEvent.event.description && ( +

+ {hoveredEvent.event.description} +

+ )} +
+ {hoveredEvent.event.start.dateTime ? ( + <> +

+ Start: {formatEventTime(hoveredEvent.event.start.dateTime)} +

+

End: {formatEventTime(hoveredEvent.event.end.dateTime)}

+ + ) : ( +

All day event

+ )} + {hoveredEvent.event.location && ( +

+ Location: {hoveredEvent.event.location} +

+ )} +
+
+ )} +
+
+ ); +}; + +export default Calendar; diff --git a/src/pages/events.astro b/src/pages/events.astro index 50b0e45..fd969d6 100644 --- a/src/pages/events.astro +++ b/src/pages/events.astro @@ -5,6 +5,10 @@ import { Image } from "astro:assets"; import eventborder from "../images/eventborder.png"; import calendar from "../images/calendar2.png"; import UpcomingEvents from "../components/events/UpcomingEvents.astro"; +import Calendar from "../components/events/Calendar.jsx"; + +const CALENDAR_API_KEY = import.meta.env.CALENDAR_API_KEY; +const EVENT_CALENDAR_ID = import.meta.env.EVENT_CALENDAR_ID; --- @@ -21,6 +25,10 @@ import UpcomingEvents from "../components/events/UpcomingEvents.astro";
- calendar +
From 142c843195ac8365ad36979e53480251742d3838 Mon Sep 17 00:00:00 2001 From: chark1es Date: Mon, 13 Jan 2025 02:09:36 -0800 Subject: [PATCH 7/7] sample env --- .env.example | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e153873 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +CALENDAR_API_KEY=YOUR_API_KEY +EVENT_CALENDAR_ID=YOUR_EVENT_CALENDAR_ID \ No newline at end of file