Skip to content

Commit

Permalink
Events Page (#212)
Browse files Browse the repository at this point in the history
* added route for events page

* deleted everything

* Events cards (#214)

* completed rough event card

* possible fix

* remove console log

* fix padding (#213)

* fix text colors for different orgs

* responsive navbar (#215)

* started responsive navbar

* commit typing

* finish navbar

* move navbar styles to component level module

* commit module typing

* use proper color variables

* use seconds for transitions

* fix navlink order

* fix easing functions

* add css comments

* Fetch data for board cards at build time server-side (#211)

* fetch board data from spreadsheet and populate

* moved code to api util function

* isr generate date once a day

* delete hardcoded data

* fixed board card fetch

* fixes

* Update BoardAPI.ts

* dumb fix

* Update Navbar.module.scss (#216)

* fix navbar with events tab

* optional facebook url

* Event filters (#219)

* completed rough event card

* possible fix

* remove console log

* fix padding (#213)

* fix text colors for different orgs

* responsive navbar (#215)

* started responsive navbar

* commit typing

* finish navbar

* move navbar styles to component level module

* commit module typing

* use proper color variables

* use seconds for transitions

* fix navlink order

* fix easing functions

* add css comments

* Fetch data for board cards at build time server-side (#211)

* fetch board data from spreadsheet and populate

* moved code to api util function

* isr generate date once a day

* delete hardcoded data

* fixed board card fetch

* fixes

* Update BoardAPI.ts

* dumb fix

* Update Navbar.module.scss (#216)

* fix navbar with events tab

* optional facebook url

* close on click (#218)

* add event card filters

* make footer icon type pointer

hi ronak - chris

* breakpoint as variable

* remove old files

* Event details (#220)

* wire up dynamic routes from api and link from corresponding card

* rename /event to /events

* format event name in url

* mobile at 960

* event page details + calendar actions

* small fixes

* fix committee name and css

* remove unused link for now

* fix replaceAll not supported

* generate and cache paths server side

* add support for viewing past events

* fix some comments

* SEO event link previews

* pass entire event directly to event card

* update seo description

* mobile dropdown functioning

* fix 2-way binding
  • Loading branch information
farisashai authored Mar 25, 2022
1 parent 9f87949 commit 70048aa
Show file tree
Hide file tree
Showing 22 changed files with 998 additions and 40 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"isready": "npm run format && npm run lint && npm run build"
},
"dependencies": {
"ics": "^2.35.0",
"next": "12.1.0",
"react": "17.0.2",
"react-countup": "^6.1.1",
Expand Down
50 changes: 50 additions & 0 deletions pages/events/[key].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import NotFoundPage from "pages/404";
import { EventObject, getAllEvents, getEvent } from "src/api/EventsAPI";
import SEO from "src/components/SEO";
import EventContent from "src/sections/event/Event.Content";
import { formatURLEventTitle, getDateTime } from "src/utils";

const EventPage: React.FC<{ event: EventObject }> = ({ event }) => {
if (!event) return <NotFoundPage />;
return (
<>
<SEO
title={event.title}
path={`${formatURLEventTitle(event.title)}-${event.uuid}`}
description={`${event.location} - ${getDateTime(event).time}\n\nEvent Description: ${
event.description
}`}
image={event.cover}
/>
<EventContent event={event} />;
</>
);
};

export default EventPage;

// Only render dynamic paths for actual events, don't need a fallback to generate if the link is wrong
export async function getStaticPaths() {
const events = await getAllEvents("future");
return {
paths: events.map(event => ({
params: {
key: `${formatURLEventTitle(event.title)}-${event.uuid}`,
},
})),
fallback: "blocking",
};
}

// UUID is always 36 characters long at the end of the url, use this to go to correct event details
const UUID_LEN = 36;
export async function getStaticProps({ params }) {
const key = params.key;
const uuid = key.slice(-UUID_LEN);
const event = await getEvent(uuid);
return {
props: {
event,
},
};
}
24 changes: 24 additions & 0 deletions pages/events/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { EventsArray, getAllEvents } from "src/api/EventsAPI";
import SEO from "src/components/SEO";
import EventsContent from "src/sections/events/Events.Content";

const EventsPage: React.FC<{ futureEvents: EventsArray }> = ({ futureEvents }) => {
return (
<>
<SEO title="Events" path="/events" />
<EventsContent events={futureEvents} />
</>
);
};

export default EventsPage;

export async function getStaticProps() {
const futureEvents = await getAllEvents("future");
return {
props: {
futureEvents: futureEvents || [],
},
revalidate: 1 * 60 * 60,
};
}
2 changes: 1 addition & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const IndexPage: React.FC<{ events: Array<EventObject> }> = ({ events }) => (
export default IndexPage;

export async function getStaticProps() {
const events = await getAllEvents();
const events = await getAllEvents("future");

return {
props: {
Expand Down
9 changes: 9 additions & 0 deletions public/assets/calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/assets/closemenubutton.svg

This file was deleted.

1 change: 0 additions & 1 deletion public/assets/menubutton.svg

This file was deleted.

104 changes: 88 additions & 16 deletions src/api/EventsAPI.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
export type EventsResponse = {
error: unknown;
events: EventsArray;
};

export type EventsArray = EventObject[];
import * as ics from "ics";
import { formatURLEventTitle } from "src/utils";
const EVENT_API = "https://api.acmucsd.com/api/v2/event";

export type EventObject = {
uuid: string;
Expand All @@ -19,30 +16,105 @@ export type EventObject = {
pointValue: number;
requiresStaff: boolean;
staffPointBonus: number;
facebookUrl?: string;
};

const handleErrors = (response: Response): Promise<EventsResponse> => {
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 getAllEvents = async (): Promise<EventsArray | undefined> => {
let apiurl = "https://api.acmucsd.com/api/v2/event/future";

// TODO: Fix test data for development purposes (images are not showing up)
// if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
// apiurl = "https://testing.api.acmucsd.com/api/v2/event/future"
// }
const getAllEvents = async (
type: "past" | "future" | "" = ""
): Promise<EventsArray | undefined> => {
const api_url = `${EVENT_API}/${type}`;

try {
const response: Response = await fetch(apiurl);
const response: Response = await fetch(api_url);
const result: EventsResponse = await handleErrors(response);
return result.events;
} catch (error) {
return undefined;
}
};

export { getAllEvents };
const getEvent = async (uuid: string): Promise<EventObject | undefined> => {
let 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;
}
};

// Referencing this answer to save text in a file and download it
// https://stackoverflow.com/questions/44656610/download-a-string-as-txt-file-in-react
const createDownloadFile = (textContent: string, title: string): void => {
const element = document.createElement("a");
const file = new Blob([textContent], { type: "text/plain" });
element.href = URL.createObjectURL(file);
element.download = `${title}.ics`;
document.body.appendChild(element);
element.click();
};

const downloadICS = (event: EventObject): void => {
const startDate = new Date(event.start);
const endDate = new Date(event.end);
const duration = endDate.getTime() - startDate.getTime();

const attributes: ics.EventAttributes = {
start: [
startDate.getFullYear(),
startDate.getMonth() + 1, // Library is 1-indexed rather than 0-indexed
startDate.getDate(),
startDate.getHours(),
startDate.getMinutes(),
],
duration: { minutes: duration / (1000 * 60) },
title: event.title,
description: event.description,
location: event.location,
url: `https://acmucsd.com/events/${formatURLEventTitle(event.title)}-${event.uuid}`,
organizer: { name: "ACM at UCSD", email: "[email protected]" },
};

const response = ics.createEvent(attributes);

createDownloadFile(response.value, formatURLEventTitle(event.title));
};

// Referencing this link format - no official API documentation exists
// https://github.com/InteractionDesignFoundation/add-event-to-calendar-docs/blob/main/services/google.md
const saveToGoogleCal = ({ title, description, location, start, end }: EventObject): void => {
const url = new URL("https://www.google.com/calendar/render?action=TEMPLATE");
const params = new URLSearchParams(url.search);
params.append("text", title);
params.append("details", description);
params.append("location", location);
const startISO = start.replaceAll("-", "").replaceAll(":", "").replaceAll(".", "");
const endISO = end.replaceAll("-", "").replaceAll(":", "").replaceAll(".", "");
params.append("dates", `${startISO}/${endISO}`);

const GCAL_LINK = `${url.origin}${url.pathname}?${params.toString()}`;
window.open(GCAL_LINK);
};

export { getEvent, getAllEvents, downloadICS, saveToGoogleCal };
99 changes: 99 additions & 0 deletions src/components/EventCard/EventCard.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
@use "src/styles/colors" as colors;

.card {
width: 100%;
height: 177px;
background: colors.$white;
box-shadow: 0px 1px 6px colors.$black;
border-radius: 20px;
position: relative;
padding: 0.75rem;
display: grid;
cursor: pointer;
grid-template-columns: 1fr;
grid-template-rows: 2rem 1fr 20px;

.cardHeader {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: baseline;

.cardDate {
font-family: DM Sans;
font-style: normal;
font-weight: normal;
font-size: 25px;
line-height: 100%;
color: colors.$black;
margin: 0;
display: inline;
}
.cardDay {
display: inline;
margin: 0 0 0 10px;
font-family: DM Sans;
font-style: normal;
font-weight: normal;
font-size: 11px;
line-height: 100%;
color: colors.$black;
text-transform: uppercase;
}
}

.cardBody {
display: flex;
flex-flow: column;
justify-content: center;

.eventTitle {
font-family: DM Sans;
font-style: normal;
font-weight: bold;
font-size: 15px;
line-height: 110%;
white-space: pre-wrap; /*keep text on one line */
overflow: hidden; /*prevent text from being shown outside the border */
text-overflow: ellipsis; /*cut off text with an ellipsis*/
max-height: 40px;
margin: 0;
&.general {
color: colors.$blue;
}
&.innovate {
color: colors.$purple;
}
&.hack {
color: colors.$orange;
}
&.cyber {
color: colors.$turquoise;
}
&.ai {
color: colors.$red;
}
}
.eventLocation,
.eventTime {
font-family: DM Sans;
font-style: normal;
font-weight: normal;
font-size: 13px;
line-height: 17px;

color: colors.$black;
margin: 0;
}
}
.cardFooter {
.footerIcon {
width: 20px;
height: 20px;

filter: invert(1);
margin: 0;
cursor: pointer;
}
}
}
25 changes: 25 additions & 0 deletions src/components/EventCard/EventCard.module.scss.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// AUTOGENERATED FILE -- DO NOT EDIT DIRECTLY

declare namespace EventCardModuleScssNamespace {
export interface IEventCardModuleScss {
ai: string;
card: string;
cardBody: string;
cardDate: string;
cardDay: string;
cardFooter: string;
cardHeader: string;
cyber: string;
eventLocation: string;
eventTime: string;
eventTitle: string;
footerIcon: string;
general: string;
hack: string;
innovate: string;
}
}

declare const EventCardModuleScssModule: EventCardModuleScssNamespace.IEventCardModuleScss;

export = EventCardModuleScssModule;
Loading

1 comment on commit 70048aa

@vercel
Copy link

@vercel vercel bot commented on 70048aa Mar 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.