diff --git a/pages/index.mdx b/pages/index.mdx index 9bffb72..7bfaf31 100644 --- a/pages/index.mdx +++ b/pages/index.mdx @@ -1,2 +1,22 @@ import HomePage from '../src/homepage'; -export default HomePage; \ No newline at end of file +import { useSSG } from 'nextra/ssg'; +import { getAllHackEvents, getHackEvent } from '../src/api/events_api'; + +export async function getStaticProps() { + const uuids = ["1668587f-5816-45cc-8b5e-1a4825dff9e4", "2ba9e5ed-614e-4c19-9d7c-ab2cfd57e140", "73fc61ef-b3db-46c4-a5bf-71aba152e2f6", "3b922d84-9c57-4f2a-9e76-19e4e9130fe2", "6124d23b-ab77-481f-9248-920565ac07f3", "fdd1c33f-f31b-4421-abc9-0887158147cc"] + const promises = uuids.map(uuid => getHackEvent(uuid)) + const past_events = await Promise.all(promises); + const future_events = await getAllHackEvents('future'); + + return { + props: { + ssg: { + past_events: past_events || [], + future_events: future_events || [] + } + }, + revalidate: 1 * 60 * 60, // once every hour (in seconds) + }; +} + + diff --git a/public/assets/left_diamonds.svg b/public/assets/left_diamonds.svg index 0b336b3..91095d6 100644 --- a/public/assets/left_diamonds.svg +++ b/public/assets/left_diamonds.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/public/assets/right_diamonds.svg b/public/assets/right_diamonds.svg index 031aec5..70d7785 100644 --- a/public/assets/right_diamonds.svg +++ b/public/assets/right_diamonds.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/api/events_api.tsx b/src/api/events_api.tsx new file mode 100644 index 0000000..ae3e251 --- /dev/null +++ b/src/api/events_api.tsx @@ -0,0 +1,66 @@ +// Modified from https://github.com/acmucsd/main-website/blob/main/src/api/EventsAPI.ts +const EVENT_API = 'https://api.acmucsd.com/api/v2/event'; + +export type EventObject = { + uuid: string; + organization: string; + committee: string; + cover: string; + title: string; + description: string; + location: string; + eventLink?: string; + start: string; + end: string; + pointValue: number; + requiresStaff: boolean; + staffPointBonus: number; +}; + +export type EventsArray = EventObject[]; + +export type EventsResponse = { + error: unknown; + events: EventsArray; +}; + +export type EventResponse = { + error: unknown; + event: EventObject; +}; + +const handleErrors = (response: Response) => { + if (!response.ok) { + throw Error(response.statusText); + } + return response.json(); +}; + +const getAllHackEvents = async ( + type: 'past' | 'future' | '' = '' +): Promise => { + const api_url = `${EVENT_API}/${type}`; + + try { + const response: Response = await fetch(api_url); + const result: EventsResponse = await handleErrors(response); + const filteredEvents = result.events.filter(event => event.committee === 'Hack'); + return filteredEvents; + } catch (error) { + return undefined; + } +}; + +const getHackEvent = async (uuid: string): Promise => { + const api_url = `${EVENT_API}/${uuid}`; + + try { + const response: any = await fetch(api_url); + const result: EventResponse = await handleErrors(response); + return result.event; + } catch (error) { + return undefined; + } +}; + +export { getAllHackEvents, getHackEvent }; diff --git a/src/components/event-card/index.tsx b/src/components/event-card/index.tsx index ea1acad..62c6c4b 100644 --- a/src/components/event-card/index.tsx +++ b/src/components/event-card/index.tsx @@ -1,7 +1,52 @@ import styles from './style.module.css'; +import { EventObject } from '../../api/events_api'; -const EventsCard: React.FC = () => { - return

Event Card

; +const EventsCard: React.FC<{ event: EventObject }> = ({ event }) => { + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'June', + 'July', + 'Aug', + 'Sept', + 'Oct', + 'Nov', + 'Dec', + ]; + const days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat']; + const date = new Date(event.start).getDate(); + const month = months[new Date(event.start).getMonth()]; + const day = days[new Date(event.start).getDay()]; + const year = new Date(event.start).getFullYear(); + const { uuid, title, cover, location } = event; + const formatTitle = (title: string): string => { + return encodeURIComponent(title.toLowerCase().trim().replace(/ /g, '-')); + }; + return ( +
+ +
+
+

+ {month} + {date} {year} +

+

{day}

+
+
+ Event cover +
+
+

{title}

+

{location}

+
+
+
+
+ ); }; export default EventsCard; diff --git a/src/components/event-card/style.module.css b/src/components/event-card/style.module.css index e69de29..363e038 100644 --- a/src/components/event-card/style.module.css +++ b/src/components/event-card/style.module.css @@ -0,0 +1,38 @@ +.card { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + height: 100%; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + border: 2px solid #333; + border-radius: 20px; + position: relative; + padding: 1rem; + cursor: pointer; +} + +.card_header { + display: flex; + justify-content: space-between; + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.card_cover { + flex-grow: 1; + width: 100%; + margin-bottom: 1rem; +} + +.card_image { + border-radius: 20px; +} + +.card_title { + color: var(--docs-accent-color) !important; +} + +.card_day { + font-size: 1rem; +} \ No newline at end of file diff --git a/src/homepage.tsx b/src/homepage.tsx index 5e8854e..aa04b9c 100644 --- a/src/homepage.tsx +++ b/src/homepage.tsx @@ -5,12 +5,17 @@ import About from './sections/About'; import Events from './sections/Events'; import Team from './sections/Team'; -const HomePage: NextPage = () => { +import { EventsArray } from './api/events_api'; + +const HomePage: NextPage<{ past_events: EventsArray; future_events: EventsArray }> = ({ + past_events, + future_events, +}) => { return (
- +
); diff --git a/src/sections/Events/index.tsx b/src/sections/Events/index.tsx index 9a48704..3f7f6f3 100644 --- a/src/sections/Events/index.tsx +++ b/src/sections/Events/index.tsx @@ -1,7 +1,48 @@ +import { useState } from 'react'; import styles from './style.module.css'; +import EventsCard from '../../components/event-card'; +import { EventsArray } from '../../api/events_api'; -const Events: React.FC = () => { - return

Event Section

; +const Events: React.FC<{ past_events: EventsArray; future_events: EventsArray }> = ({ + past_events, + future_events, +}) => { + const [selectedOption, setSelectedOption] = useState('Upcoming'); // State to store the selected option + const [filteredEvents, setFilteredEvents] = useState(future_events); + + // Function to handle option change + const handleOptionChange = (event: React.ChangeEvent) => { + const selected = event.target.value; + setSelectedOption(selected); + + // Set filtered events based on the selected option + if (selected === 'Upcoming') { + setFilteredEvents(future_events); + } else if (selected === 'Past') { + setFilteredEvents(past_events); + } + }; + + return ( +
+
+

Events

+
+ +
+
+
+ {!filteredEvents ? ( +

No upcoming events. Check again later!

+ ) : ( + filteredEvents.map(event => ) + )} +
+
+ ); }; export default Events; diff --git a/src/sections/Events/style.module.css b/src/sections/Events/style.module.css index 7b3eeae..e0b47ca 100644 --- a/src/sections/Events/style.module.css +++ b/src/sections/Events/style.module.css @@ -1,9 +1,31 @@ -.events { +.container { display: flex; - justify-content: center; + flex-direction: column; align-items: center; - width: 100%; - height: 30vh; - max-height: 100vh; - border-bottom: 1px solid black; +} +.events { + display: grid; + margin: 0 auto; + grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); + row-gap: 2rem; + column-gap: 2rem; + max-width: 90%; + text-align: center; +} + +.header { + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + font-weight: 700; +} + +.title { + font-size: 2rem; +} + +.event_type { + margin-top: 1rem; + border: 2px solid; + border-radius: 4px; } \ No newline at end of file diff --git a/src/sections/Hero/style.module.css b/src/sections/Hero/style.module.css index b436347..82c789c 100644 --- a/src/sections/Hero/style.module.css +++ b/src/sections/Hero/style.module.css @@ -7,6 +7,7 @@ background-color: var(--docs-accent-color); color: #fff; overflow: hidden; + transition: all 0.5s ease-in-out; } .backdrop { @@ -24,6 +25,7 @@ .backdrop_left, .backdrop_right { object-fit: contain; width: 100%; + height: 100%; } /* Landing Card Text */ @@ -43,6 +45,7 @@ /* Landing Card Scroll Down */ .arrow { + margin-top: auto; margin-bottom: 1em; text-align: center; font-size: 1.5em; @@ -67,11 +70,13 @@ } .backdrop { grid-template-columns: minmax(18em, 1fr); + grid-template-rows: minmax(0, 0.7fr); max-width: 95%; } .backdrop_left{ justify-self: center; - width: 70%; + max-width: 70%; + max-height: 100%; } .backdrop_right { display: none; @@ -86,10 +91,11 @@ @media screen and (min-device-width : 768px) and (max-device-width : 1024px) { .backdrop { grid-template-columns: 1fr 2fr; + grid-template-rows: 1fr; max-width: 95%; } .backdrop_left{ - width: 100%; + max-width: 100%; } .backdrop_right { display: none; @@ -116,4 +122,4 @@ html[class~='dark'] .landing_title { html[class~='dark'] .arrow { filter: brightness(85%); -} \ No newline at end of file +}