Merge pull request 'Merge Calendar to main branch' (#5) from calendar into main
Reviewed-on: Webmaster/dev-ieeeucsd-org#5 Reviewed-by: Shing Hung <shing.hung@ieeeucsd.org>
This commit is contained in:
commit
a0396f594b
9 changed files with 606 additions and 56 deletions
2
.env.example
Normal file
2
.env.example
Normal file
|
@ -0,0 +1,2 @@
|
|||
CALENDAR_API_KEY=YOUR_API_KEY
|
||||
EVENT_CALENDAR_ID=YOUR_EVENT_CALENDAR_ID
|
358
src/components/events/Calendar.jsx
Normal file
358
src/components/events/Calendar.jsx
Normal file
|
@ -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 (
|
||||
<div className="text-white">
|
||||
Error: Calendar API key is not configured
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-[90vw] mx-auto p-[3vw] relative"
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
{/* Hovering Calendar Header */}
|
||||
<div className="flex justify-center mb-[2vw]">
|
||||
<div className="bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[1.5vw] p-[1vw] backdrop-blur-sm w-[30vw] px-[2vw]">
|
||||
<div className="flex items-center gap-[3vw]">
|
||||
<button
|
||||
onClick={() => changeMonth(-1)}
|
||||
className="text-white hover:text-ieee-yellow transition-colors text-[2vw] bg-ieee-black/40 w-[4vw] h-[4vw] rounded-[1vw] flex items-center justify-center"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<h2 className="text-white text-[2.5vw] font-bold whitespace-nowrap">
|
||||
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => changeMonth(1)}
|
||||
className="text-white hover:text-gray transition-colors text-[2vw] bg-ieee-black/40 w-[4vw] h-[4vw] rounded-[1vw] flex items-center justify-center"
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Calendar Body */}
|
||||
<div className="bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[1.5vw] p-[1vw] relative">
|
||||
{/* Week Days Header */}
|
||||
<div className="grid grid-cols-7 gap-[0.5vw] mb-[1vw]">
|
||||
{weekDays.map((day, index) => (
|
||||
<div key={day} className="flex justify-center w-full">
|
||||
<div
|
||||
className={`text-white text-center font-semibold p-[0.5vw] text-[1.2vw] bg-ieee-black/60 w-full h-[4vw] flex items-center justify-center
|
||||
${
|
||||
index === 0
|
||||
? "rounded-tl-[2vw] rounded-[0.5vw]"
|
||||
: index === 6
|
||||
? "rounded-tr-[2vw] rounded-[0.5vw]"
|
||||
: "rounded-[0.5vw]"
|
||||
}`}
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Calendar Grid */}
|
||||
<div className="grid grid-cols-7 gap-[0.5vw] relative">
|
||||
{getDaysInMonth(currentDate).map((day, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`min-h-[10vw] p-[0.5vw] rounded relative ${
|
||||
day ? "bg-white/5" : "bg-transparent"
|
||||
} border border-white/10`}
|
||||
>
|
||||
{day && (
|
||||
<>
|
||||
<div className="text-white mb-[0.5vw] text-[1vw]">
|
||||
{day.getDate()}
|
||||
</div>
|
||||
<div className="space-y-[0.5vw]">
|
||||
{getEventsForDay(day).map((event, eventIndex) => (
|
||||
<div
|
||||
key={eventIndex}
|
||||
className="text-[0.8vw] border border-gray-300 text-white p-[0.5vw] rounded truncate cursor-pointer hover:bg-white/10 transition-colors relative"
|
||||
onMouseEnter={(e) => handleEventMouseEnter(event, e)}
|
||||
onMouseLeave={handleEventMouseLeave}
|
||||
>
|
||||
{event.summary}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tooltip */}
|
||||
{hoveredEvent && (
|
||||
<div
|
||||
className="fixed z-[9999] bg-ieee-blue-100 text-white p-[1vw] rounded-[0.5vw] shadow-xl border border-white/20 min-w-[15vw]"
|
||||
style={{
|
||||
left: `${tooltipPosition.x + 15}px`,
|
||||
top: `${tooltipPosition.y + 15}px`,
|
||||
}}
|
||||
>
|
||||
<h3 className="text-[1vw] font-semibold mb-[0.5vw]">
|
||||
{hoveredEvent.event.summary}
|
||||
</h3>
|
||||
{hoveredEvent.event.description && (
|
||||
<p className="text-[0.8vw] mb-[0.5vw] text-white/80">
|
||||
{hoveredEvent.event.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="text-[0.8vw] text-white/90">
|
||||
{hoveredEvent.event.start.dateTime ? (
|
||||
<>
|
||||
<p>
|
||||
Start: {formatEventTime(hoveredEvent.event.start.dateTime)}
|
||||
</p>
|
||||
<p>End: {formatEventTime(hoveredEvent.event.end.dateTime)}</p>
|
||||
</>
|
||||
) : (
|
||||
<p>All day event</p>
|
||||
)}
|
||||
{hoveredEvent.event.location && (
|
||||
<p className="mt-[0.3vw]">
|
||||
Location: {hoveredEvent.event.location}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
170
src/components/events/EventList.jsx
Normal file
170
src/components/events/EventList.jsx
Normal file
|
@ -0,0 +1,170 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const UpcomingEvent = ({ name, location, date, time, delay, description }) => (
|
||||
<div className="text-white w-[40vw] pl-[8%] border-l-[0.3vw] border-white/70 pb-[5%] relative">
|
||||
<p
|
||||
data-inview
|
||||
className={`animate-duration-500 animate-delay-${delay * 200} in-view:animate-fade-left py-[0.2%] px-[2%] w-fit border-[0.1vw] font-light rounded-full text-[1.3vw]`}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
<div
|
||||
data-inview
|
||||
className={`animate-duration-500 animate-delay-${delay * 200 + 100} in-view:animate-fade-left flex justify-between items-center min-w-[70%] w-fit text-[1.2vw] my-[2%]`}
|
||||
>
|
||||
<p>Location: {location}</p>
|
||||
{date && (
|
||||
<>
|
||||
<div className="bg-white h-[0.5vw] w-[0.5vw] rounded-full mx-[0.5vw]" />
|
||||
<p>{date}</p>
|
||||
</>
|
||||
)}
|
||||
{time && (
|
||||
<>
|
||||
<div className="bg-white h-[0.5vw] w-[0.5vw] rounded-full mx-[0.5vw]" />
|
||||
<p>{time}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
data-inview
|
||||
className={`animate-duration-500 animate-delay-${delay * 200 + 200} in-view:animate-fade-left text-[1vw] text-white/60`}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
<div className="bg-ieee-yellow h-[1.2vw] w-[1.2vw] rounded-full absolute -top-[1.5%] -left-[2%]" />
|
||||
</div>
|
||||
);
|
||||
|
||||
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 = 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: 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 (
|
||||
<div className="text-white">
|
||||
Error: Calendar API key is not configured
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{error && <p className="text-white">Error: {error}</p>}
|
||||
{!loading && !error && events.length === 0 && (
|
||||
<UpcomingEvent
|
||||
name="No Upcoming Events!"
|
||||
location="¯\_(ツ)_/¯"
|
||||
date=""
|
||||
time=""
|
||||
delay={0}
|
||||
description="There are no upcoming events! Check back again soon :)
|
||||
...or just wait for the entire page to load. This is here by default LOL"
|
||||
/>
|
||||
)}
|
||||
{!loading && !error && events.length > 0 && (
|
||||
<div>
|
||||
{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 (
|
||||
<UpcomingEvent
|
||||
key={index}
|
||||
name={event.summary || "No Title"}
|
||||
location={event.location || "No location provided"}
|
||||
date={`${day} ${date}`}
|
||||
time={time}
|
||||
delay={index}
|
||||
description={event.description || "No description available."}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventList;
|
|
@ -1,45 +1,29 @@
|
|||
---
|
||||
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;
|
||||
---
|
||||
<div class="flex ml-[15%] my-[10%]">
|
||||
<div class="w-1/4 text-white pr-[5%] mr-[10%]">
|
||||
<div class="w-[6vw] h-[0.3vw] bg-ieee-yellow rounded-full"/>
|
||||
<div class="flex items-center text-[2vw] font-bold my-[10%]">
|
||||
<LiaDotCircle className=" mr-[1vw] text-[2.5vw]"/>
|
||||
<p>
|
||||
Upcoming <br/> Events
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-[1.3vw] font-light">
|
||||
SCROLL DOWN TO SEE THE UPCOMING EVENTS FOR IEEE!
|
||||
|
||||
<div class="flex ml-[15%] my-[10%]">
|
||||
<div class="w-1/4 text-white pr-[5%] mr-[10%]">
|
||||
<div class="w-[6vw] h-[0.3vw] bg-ieee-yellow rounded-full"></div>
|
||||
<div class="flex items-center text-[2vw] font-bold my-[10%]">
|
||||
<LiaDotCircle className=" mr-[1vw] text-[2.5vw]" />
|
||||
<p>
|
||||
Upcoming <br /> Events
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<UpcomingEvent
|
||||
name="Beach Social"
|
||||
location="La Jolla Cove"
|
||||
date="Tue 9 Jun 2024"
|
||||
time="2 pm"
|
||||
delay="0"
|
||||
description="Cursus nec orci pulvinar convallis mollis a diam. Nostra aptent praesent suscipit nisl dignissim consequat. Semper malesuada pharetra fusce; elementum maximus"
|
||||
/>
|
||||
<UpcomingEvent
|
||||
name="Beach Social"
|
||||
location="La Jolla Cove"
|
||||
date="Tue 9 Jun 2024"
|
||||
time="2 pm"
|
||||
delay="300"
|
||||
description="Cursus nec orci pulvinar convallis mollis a diam. Nostra aptent praesent suscipit nisl dignissim consequat. Semper malesuada pharetra fusce; elementum maximus"
|
||||
/>
|
||||
<UpcomingEvent
|
||||
name="Beach Social"
|
||||
location="La Jolla Cove"
|
||||
date="Tue 9 Jun 2024"
|
||||
time="2 pm"
|
||||
delay="700"
|
||||
description="Cursus nec orci pulvinar convallis mollis a diam. Nostra aptent praesent suscipit nisl dignissim consequat. Semper malesuada pharetra fusce; elementum maximus"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-[1.3vw] font-light">
|
||||
SCROLL DOWN TO SEE THE UPCOMING EVENTS FOR IEEE!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EventList
|
||||
client:load
|
||||
CALENDAR_API_KEY={CALENDAR_API_KEY}
|
||||
EVENT_CALENDAR_ID={EVENT_CALENDAR_ID}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
20
src/components/join/BuildingLocation.astro
Normal file
20
src/components/join/BuildingLocation.astro
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
data-inview
|
||||
class="flex flex-col items-center w-[40vw] my-[10%] in-view:animate-fade-down"
|
||||
>
|
||||
<p class="text-ieee-yellow text-[1.2vw]">Each officer has their own OAH</p>
|
||||
<p class="text-white text-[2.5vw] my-[2%]">Check out our calendar!</p>
|
||||
<iframe
|
||||
class="object-cover aspect-[1000/400] w-[90%] rounded-[1.5vw]"
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3350.627565860723!2d-117.23806952372503!3d32.881572078634505!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x80dc06c3689b4f99%3A0xdf55f97f07f34d4f!2sIrwin%20%26%20Joan%20Jacobs%20School%20of%20Engineering!5e0!3m2!1sen!2sus!4v1736755644414!5m2!1sen!2sus"
|
||||
width="600"
|
||||
height="450"
|
||||
style="border:0;"
|
||||
allowfullscreen=""
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||
</div>
|
|
@ -1,8 +1,10 @@
|
|||
---
|
||||
import OH from "./OH.astro"
|
||||
import OAH from "./OAH.astro";
|
||||
import BuildingLocation from "./BuildingLocation.astro";
|
||||
---
|
||||
|
||||
<div class="w-full flex justify-evenly px-[10%]">
|
||||
<OH title="Check out our calendar!" subtitle="Each officer has their own OAH" image="/calendar.png" />
|
||||
<OH title="Check out our location!" subtitle="We are located in EBU1-4710" image="/map.png" />
|
||||
<OAH />
|
||||
|
||||
<BuildingLocation />
|
||||
</div>
|
19
src/components/join/OAH.astro
Normal file
19
src/components/join/OAH.astro
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
data-inview
|
||||
class="flex flex-col items-center w-[40vw] my-[10%] in-view:animate-fade-down"
|
||||
>
|
||||
<p class="text-ieee-yellow text-[1.2vw]">Each officer has their own OAH</p>
|
||||
<p class="text-white text-[2.5vw] my-[2%]">Check out our calendar!</p>
|
||||
<iframe
|
||||
class="object-cover aspect-[1000/400] w-[90%] rounded-[1.5vw]"
|
||||
src="https://calendar.google.com/calendar/embed?src=c_62493071bab19c7c60d103460604dc7b3b569ffc1e58a42617978f626dff02ac%40group.calendar.google.com&ctz=America%2FLos_Angeles"
|
||||
style="border: 0"
|
||||
width="600"
|
||||
height="450"
|
||||
frameborder="0"
|
||||
scrolling="no"></iframe>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
const {title, subtitle, image} = Astro.props;
|
||||
---
|
||||
|
||||
<div data-inview class="flex flex-col items-center w-[40vw] my-[10%] in-view:animate-fade-down">
|
||||
<p class="text-ieee-yellow text-[1.2vw]">
|
||||
{subtitle}
|
||||
</p>
|
||||
<p class="text-white text-[2.5vw] my-[2%]">
|
||||
{title}
|
||||
</p>
|
||||
<img src={image} alt="Office hours" class="object-cover aspect-[620/408] w-[90%] rounded-[2vw]" />
|
||||
</div>
|
|
@ -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;
|
||||
---
|
||||
|
||||
<Layout>
|
||||
|
@ -21,6 +25,10 @@ import UpcomingEvents from "../components/events/UpcomingEvents.astro";
|
|||
<EventTitle />
|
||||
<UpcomingEvents />
|
||||
<div class="flex justify-center pb-[10%]">
|
||||
<Image src={calendar} alt="calendar" class="w-[70%]" />
|
||||
<Calendar
|
||||
client:load
|
||||
CALENDAR_API_KEY={CALENDAR_API_KEY}
|
||||
EVENT_CALENDAR_ID={EVENT_CALENDAR_ID}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
|
|
Loading…
Reference in a new issue