I changed some other code along the way ... which makes this PR slightly long :') rip. closes #472 takes a stab at #466 https://csclub.uwaterloo.ca/~a3thakra/csc/adi-member-json-api/api/members.json Reviewed-on: #489 Reviewed-by: Amy <a258wang@csclub.uwaterloo.ca>
This commit is contained in:
parent
8e0e446fd9
commit
be308f6249
|
@ -37,6 +37,13 @@ steps:
|
||||||
commands:
|
commands:
|
||||||
- npm run build:calendar
|
- npm run build:calendar
|
||||||
|
|
||||||
|
- name: generate-api
|
||||||
|
image: node:16
|
||||||
|
depends_on:
|
||||||
|
- install-deps
|
||||||
|
commands:
|
||||||
|
- npm run build:api
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: node:16
|
image: node:16
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -47,6 +54,7 @@ steps:
|
||||||
- name: export
|
- name: export
|
||||||
image: node:16
|
image: node:16
|
||||||
depends_on:
|
depends_on:
|
||||||
|
- generate-api
|
||||||
- generate-calendar
|
- generate-calendar
|
||||||
- build
|
- build
|
||||||
commands:
|
commands:
|
||||||
|
|
|
@ -29,3 +29,7 @@ yarn-error.log*
|
||||||
|
|
||||||
# Images should be optimized
|
# Images should be optimized
|
||||||
/public/images
|
/public/images
|
||||||
|
|
||||||
|
# APIs should be automatically generated, schema should be checked in
|
||||||
|
/public/api/*
|
||||||
|
!/public/api/schema
|
|
@ -19,7 +19,7 @@ interface EventCardProps {
|
||||||
permaLink: string;
|
permaLink: string;
|
||||||
showDescription?: boolean;
|
showDescription?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
year: string;
|
year: number;
|
||||||
term: string;
|
term: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
titleLinked: boolean;
|
titleLinked: boolean;
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface MiniEventCardProps {
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
background: "dark-bg" | "normal-bg";
|
background: "dark-bg" | "normal-bg";
|
||||||
year: string;
|
year: number;
|
||||||
term: string;
|
term: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { parse } from "date-fns";
|
import { parse } from "date-fns";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { DATE_FORMAT, getLocalDateFromEST } from "@/utils";
|
||||||
|
|
||||||
import warnings from "../content/warnings/warnings.json";
|
import warnings from "../content/warnings/warnings.json";
|
||||||
import { DATE_FORMAT, getLocalDateFromEST } from "../utils";
|
|
||||||
|
|
||||||
import styles from "./WarningHeader.module.css";
|
import styles from "./WarningHeader.module.css";
|
||||||
|
|
||||||
|
|
176
lib/events.ts
176
lib/events.ts
|
@ -6,28 +6,33 @@ import matter from "gray-matter";
|
||||||
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||||
import { serialize } from "next-mdx-remote/serialize";
|
import { serialize } from "next-mdx-remote/serialize";
|
||||||
|
|
||||||
import type { Props } from "../pages/events/[year]/[term]/index";
|
|
||||||
// do not use alias "@/utils" as generate-calendar imports a function from this file and ts-node is not compatible
|
|
||||||
import {
|
import {
|
||||||
Term,
|
Term,
|
||||||
TERMS,
|
TERMS,
|
||||||
isTerm,
|
isTerm,
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
getLocalDateFromEST,
|
getLocalDateFromEST,
|
||||||
} from "../utils";
|
TermYear,
|
||||||
|
getTermYear,
|
||||||
|
getCurrentTermYear,
|
||||||
|
} from "@/utils";
|
||||||
|
|
||||||
|
import type { Props } from "../pages/events/[year]/[term]";
|
||||||
|
|
||||||
const EVENTS_PATH = path.join("content", "events");
|
const EVENTS_PATH = path.join("content", "events");
|
||||||
|
|
||||||
export async function getEventYears(): Promise<string[]> {
|
export async function getEventYears(): Promise<number[]> {
|
||||||
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
|
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
|
||||||
.filter((dirent) => dirent.isDirectory())
|
.filter((dirent) => dirent.isDirectory())
|
||||||
.map((dirent) => dirent.name)
|
.map((dirent) => parseInt(dirent.name))
|
||||||
.sort();
|
.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEventTermsByYear(year: string): Promise<Term[]> {
|
export async function getEventTermsByYear(year: number): Promise<Term[]> {
|
||||||
return (
|
return (
|
||||||
await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true })
|
await fs.readdir(path.join(EVENTS_PATH, year.toString()), {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
.filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
|
.filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
|
||||||
.map((dirent) => dirent.name as Term)
|
.map((dirent) => dirent.name as Term)
|
||||||
|
@ -55,7 +60,7 @@ interface Metadata {
|
||||||
location: string;
|
location: string;
|
||||||
permaLink: string;
|
permaLink: string;
|
||||||
registerLink?: string;
|
registerLink?: string;
|
||||||
year: string;
|
year: number;
|
||||||
term: string;
|
term: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
@ -66,12 +71,12 @@ export interface Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEventBySlug(
|
export async function getEventBySlug(
|
||||||
year: string,
|
year: number,
|
||||||
term: Term,
|
term: Term,
|
||||||
slug: string
|
slug: string
|
||||||
): Promise<Event> {
|
): Promise<Event> {
|
||||||
const file = await fs.readFile(
|
const file = await fs.readFile(
|
||||||
path.join(EVENTS_PATH, year, term, `${slug}.md`),
|
path.join(EVENTS_PATH, year.toString(), term, `${slug}.md`),
|
||||||
"utf-8"
|
"utf-8"
|
||||||
);
|
);
|
||||||
const { content, data } = matter(file);
|
const { content, data } = matter(file);
|
||||||
|
@ -95,11 +100,11 @@ export async function getEventBySlug(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEventsByTerm(
|
export async function getEventsByTerm(
|
||||||
year: string,
|
year: number,
|
||||||
term: Term
|
term: Term
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
return (await fs.readdir(path.join(EVENTS_PATH, year, term)))
|
return (await fs.readdir(path.join(EVENTS_PATH, year.toString(), term)))
|
||||||
.filter((name) => name.endsWith(".md"))
|
.filter((name) => name.endsWith(".md"))
|
||||||
.map((name) => name.slice(0, -".md".length));
|
.map((name) => name.slice(0, -".md".length));
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -108,22 +113,24 @@ export async function getEventsByTerm(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUpcomingEvents(): Promise<Event[]> {
|
export async function getUpcomingEvents(): Promise<Event[]> {
|
||||||
const today = new Date();
|
const terms: TermYear[] = [];
|
||||||
const currentYear = today.getFullYear();
|
|
||||||
const currentTerm = Math.trunc(today.getMonth() / 4);
|
// Get events for the next two terms
|
||||||
const nextYear = currentTerm < 2 ? currentYear : currentYear + 1;
|
for (const termYear of getTermYear()) {
|
||||||
const nextTerm = (currentTerm + 1) % 3;
|
if (terms.length >= 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
terms.push(termYear);
|
||||||
|
}
|
||||||
|
|
||||||
const events: Event[] = (
|
const events: Event[] = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[
|
terms.map(async ({ year, term }) => {
|
||||||
{ year: currentYear.toString(), term: currentTerm },
|
|
||||||
{ year: nextYear.toString(), term: nextTerm },
|
|
||||||
].map(async ({ year, term }) => {
|
|
||||||
try {
|
try {
|
||||||
const eventsInTerm = await getEventsByTerm(year, TERMS[term]);
|
const eventsInTerm = await getEventsByTerm(year, term);
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
eventsInTerm.map((slug) => getEventBySlug(year, TERMS[term], slug))
|
eventsInTerm.map((slug) => getEventBySlug(year, term, slug))
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -162,12 +169,9 @@ export async function getAllEvents(): Promise<Event[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEventsPageProps({
|
export async function getEventsPageProps({
|
||||||
year,
|
|
||||||
term,
|
term,
|
||||||
}: {
|
year,
|
||||||
year: string;
|
}: TermYear): Promise<Props> {
|
||||||
term: Term;
|
|
||||||
}): Promise<Props> {
|
|
||||||
const eventNames = await getEventsByTerm(year, term);
|
const eventNames = await getEventsByTerm(year, term);
|
||||||
|
|
||||||
const events: Event[] = (
|
const events: Event[] = (
|
||||||
|
@ -198,40 +202,43 @@ export async function getEventsPageProps({
|
||||||
currentDate
|
currentDate
|
||||||
);
|
);
|
||||||
|
|
||||||
const current = getCurrentTerm();
|
|
||||||
|
|
||||||
const eventYears = await getEventYears();
|
const eventYears = await getEventYears();
|
||||||
|
|
||||||
const minYear = eventYears[0];
|
const minYear = eventYears[0];
|
||||||
const pastTerms: { year: string; term: Term }[] = [];
|
const pastTerms: TermYear[] = [];
|
||||||
let curPastYear = year;
|
|
||||||
let curPastTerm = term;
|
for (const current of getTermYear(
|
||||||
while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) {
|
{ year, term },
|
||||||
const pastTerm = getPastTerm(curPastYear, curPastTerm);
|
{ goBackwards: true, skipCurrent: true }
|
||||||
curPastYear = pastTerm.year;
|
)) {
|
||||||
curPastTerm = pastTerm.term;
|
if (pastTerms.length >= 2 || current.year < minYear) {
|
||||||
if ((await getEventsByTerm(curPastYear, curPastTerm)).length !== 0) {
|
break;
|
||||||
pastTerms.push(pastTerm);
|
}
|
||||||
|
|
||||||
|
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
|
||||||
|
pastTerms.push(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pastTerms.reverse();
|
pastTerms.reverse();
|
||||||
|
|
||||||
const maxYear = eventYears[eventYears.length - 1];
|
const maxYear = eventYears[eventYears.length - 1];
|
||||||
const futureTerms: { year: string; term: Term }[] = [];
|
const futureTerms: TermYear[] = [];
|
||||||
let curFutureYear = year;
|
|
||||||
let curFutureTerm = term;
|
for (const current of getTermYear(
|
||||||
while (
|
{ year, term },
|
||||||
parseInt(curFutureYear) <= parseInt(maxYear) &&
|
{ goBackwards: false, skipCurrent: true }
|
||||||
futureTerms.length < 2
|
)) {
|
||||||
) {
|
if (futureTerms.length >= 2 || maxYear < current.year) {
|
||||||
const futureTerm = getFutureTerm(curFutureYear, curFutureTerm);
|
break;
|
||||||
curFutureYear = futureTerm.year;
|
}
|
||||||
curFutureTerm = futureTerm.term;
|
|
||||||
if ((await getEventsByTerm(curFutureYear, curFutureTerm)).length !== 0) {
|
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
|
||||||
futureTerms.push(futureTerm);
|
futureTerms.push(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const current = getCurrentTermYear();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
year: year,
|
year: year,
|
||||||
term: term,
|
term: term,
|
||||||
|
@ -242,70 +249,3 @@ export async function getEventsPageProps({
|
||||||
futureTerms: futureTerms,
|
futureTerms: futureTerms,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentTerm(): { year: string; term: Term } {
|
|
||||||
const today = new Date().toLocaleDateString("en-CA", {
|
|
||||||
timeZone: "EST",
|
|
||||||
year: "numeric",
|
|
||||||
month: "numeric",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
|
|
||||||
const [year] = today.split("-");
|
|
||||||
|
|
||||||
let term = "";
|
|
||||||
|
|
||||||
if (`${year}-01-01` <= today) {
|
|
||||||
term = "winter";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (`${year}-05-01` <= today) {
|
|
||||||
term = "spring";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (`${year}-09-01` <= today) {
|
|
||||||
term = "fall";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTerm(term)) {
|
|
||||||
throw new Error("Error setting the current term");
|
|
||||||
}
|
|
||||||
|
|
||||||
return { year, term };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPastTerm(year: string, term: Term): { year: string; term: Term } {
|
|
||||||
const index = TERMS.indexOf(term);
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
throw new Error(`[getPastTerm] Not a valid term: "${term}" "${year}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return index === 0
|
|
||||||
? {
|
|
||||||
year: (parseInt(year) - 1).toString(),
|
|
||||||
term: TERMS[TERMS.length - 1],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
year: year,
|
|
||||||
term: TERMS[index - 1],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFutureTerm(year: string, term: Term): { year: string; term: Term } {
|
|
||||||
const index = TERMS.indexOf(term);
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
throw new Error(`[getFutureTerm] Not a valid term: "${term}" "${year}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return index === TERMS.length - 1
|
|
||||||
? {
|
|
||||||
year: (parseInt(year) + 1).toString(),
|
|
||||||
term: TERMS[0],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
year: year,
|
|
||||||
term: TERMS[index + 1],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ export interface Member {
|
||||||
program: string;
|
program: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMembers(year: string, term: Term): Promise<Member[]> {
|
export async function getMembers(year: number, term: Term): Promise<Member[]> {
|
||||||
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
||||||
return dummyMembers;
|
return dummyMembers;
|
||||||
}
|
}
|
||||||
|
|
39
lib/news.ts
39
lib/news.ts
|
@ -7,9 +7,16 @@ import truncateMarkdown from "markdown-truncate";
|
||||||
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||||
import { serialize } from "next-mdx-remote/serialize";
|
import { serialize } from "next-mdx-remote/serialize";
|
||||||
|
|
||||||
import { isTerm, Term, TERMS } from "@/utils";
|
import {
|
||||||
|
isTerm,
|
||||||
import { DATE_FORMAT, getLocalDateFromEST } from "../utils";
|
Term,
|
||||||
|
TERMS,
|
||||||
|
DATE_FORMAT,
|
||||||
|
getLocalDateFromEST,
|
||||||
|
TermYear,
|
||||||
|
getTermYear,
|
||||||
|
getCurrentTermYear,
|
||||||
|
} from "@/utils";
|
||||||
|
|
||||||
export const NEWS_PATH = path.join("content", "news");
|
export const NEWS_PATH = path.join("content", "news");
|
||||||
|
|
||||||
|
@ -96,23 +103,27 @@ export async function getNewsBySlug(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRecentNews(): Promise<News[]> {
|
export async function getRecentNews(): Promise<News[]> {
|
||||||
const today = new Date();
|
const terms: TermYear[] = [];
|
||||||
const currentYear = today.getFullYear();
|
|
||||||
const currentTerm = Math.trunc(today.getMonth() / 4);
|
// Get news for the last two terms
|
||||||
const prevYear = currentTerm > 0 ? currentYear : currentYear - 1;
|
for (const termYear of getTermYear(getCurrentTermYear(), {
|
||||||
const prevTerm = (currentTerm - 1 + 3) % 3;
|
goBackwards: true,
|
||||||
|
})) {
|
||||||
|
if (terms.length >= 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
terms.push(termYear);
|
||||||
|
}
|
||||||
|
|
||||||
const news: News[] = (
|
const news: News[] = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[
|
terms.map(async ({ year, term }) => {
|
||||||
{ year: currentYear.toString(), term: currentTerm },
|
|
||||||
{ year: prevYear.toString(), term: prevTerm },
|
|
||||||
].map(async ({ year, term }) => {
|
|
||||||
try {
|
try {
|
||||||
const newsInTerm = await getNewsByTerm(year, TERMS[term]);
|
const newsInTerm = await getNewsByTerm(year.toString(), term);
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
newsInTerm.map((slug) => {
|
newsInTerm.map((slug) => {
|
||||||
return getNewsBySlug(year, TERMS[term], slug, true);
|
return getNewsBySlug(year.toString(), term, slug, true);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
89
lib/team.ts
89
lib/team.ts
|
@ -5,12 +5,10 @@ import matter from "gray-matter";
|
||||||
import { Client } from "ldapts";
|
import { Client } from "ldapts";
|
||||||
import { serialize } from "next-mdx-remote/serialize";
|
import { serialize } from "next-mdx-remote/serialize";
|
||||||
|
|
||||||
import { getCurrentTerm } from "@/lib/events";
|
import { capitalize, TermYear } from "@/utils";
|
||||||
import { capitalize } from "@/utils";
|
|
||||||
|
|
||||||
const EXECS_PATH = path.join("content", "team", "execs");
|
const EXECS_PATH = path.join("content", "team", "execs");
|
||||||
const FILETYPE = ".md";
|
const FILETYPE = ".md";
|
||||||
const { year, term } = getCurrentTerm();
|
|
||||||
|
|
||||||
const execPositions: { [position: string]: string } = {
|
const execPositions: { [position: string]: string } = {
|
||||||
president: "President",
|
president: "President",
|
||||||
|
@ -34,7 +32,54 @@ export interface Metadata {
|
||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExecNamePosPairs() {
|
export async function getExecs(termYear: TermYear) {
|
||||||
|
const execNamePosPairs = await getExecNamePosPairs(termYear);
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
execNamePosPairs.map((namePosPair) =>
|
||||||
|
getExec(namePosPair[0], namePosPair[1])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExec(name: string, pos: string) {
|
||||||
|
let content, metadata;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = await readFile(path.join(EXECS_PATH, `${name}${FILETYPE}`));
|
||||||
|
({ content, data: metadata } = matter(raw));
|
||||||
|
|
||||||
|
const image = await getMemberImagePath(metadata.name as string);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: await serialize(content),
|
||||||
|
metadata: { ...metadata, image } as Metadata,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
// Capitalize the first letter of the first name and last name
|
||||||
|
const firstName = capitalize(name.split("-")[0]);
|
||||||
|
const lastName = capitalize(name.split("-")[1]);
|
||||||
|
|
||||||
|
const posName = execPositions[pos];
|
||||||
|
content = "Coming Soon!";
|
||||||
|
metadata = {
|
||||||
|
name: `${firstName} ${lastName}`,
|
||||||
|
role: `${posName}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const image = await getMemberImagePath(metadata.name);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: await serialize(content),
|
||||||
|
metadata: { ...metadata, image } as Metadata,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExecNamePosPairs({
|
||||||
|
term,
|
||||||
|
year,
|
||||||
|
}: TermYear): Promise<[person: string, position: string][]> {
|
||||||
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
||||||
return [["codey", "mascot"]];
|
return [["codey", "mascot"]];
|
||||||
}
|
}
|
||||||
|
@ -45,7 +90,7 @@ export async function getExecNamePosPairs() {
|
||||||
|
|
||||||
// position: name
|
// position: name
|
||||||
const execMembers: { [position: string]: string } = {};
|
const execMembers: { [position: string]: string } = {};
|
||||||
let formattedExec: [string, string][] = [];
|
let formattedExec: [person: string, position: string][] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.bind("", "");
|
await client.bind("", "");
|
||||||
|
@ -89,40 +134,6 @@ export async function getExecNamePosPairs() {
|
||||||
return formattedExec;
|
return formattedExec;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExec(name: string, pos: string, convert = true) {
|
|
||||||
let content, metadata;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const raw = await readFile(path.join(EXECS_PATH, `${name}${FILETYPE}`));
|
|
||||||
({ content, data: metadata } = matter(raw));
|
|
||||||
|
|
||||||
const image = await getMemberImagePath(metadata.name as string);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: convert ? await serialize(content) : content,
|
|
||||||
metadata: { ...metadata, image } as Metadata,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
// Capitalize the first letter of the first name and last name
|
|
||||||
const firstName = capitalize(name.split("-")[0]);
|
|
||||||
const lastName = capitalize(name.split("-")[1]);
|
|
||||||
|
|
||||||
const posName = execPositions[pos];
|
|
||||||
content = "Coming Soon!";
|
|
||||||
metadata = {
|
|
||||||
name: `${firstName} ${lastName}`,
|
|
||||||
role: `${posName}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const image = await getMemberImagePath(metadata.name);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: convert ? await serialize(content) : content,
|
|
||||||
metadata: { ...metadata, image } as Metadata,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getImage(imgPath: string) {
|
async function getImage(imgPath: string) {
|
||||||
try {
|
try {
|
||||||
await access(path.join("public", imgPath));
|
await access(path.join("public", imgPath));
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
"postcss-flexbugs-fixes": "^5.0.2",
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
"postcss-preset-env": "^7.0.0",
|
"postcss-preset-env": "^7.0.0",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
|
"tsconfig-paths": "^4.1.0",
|
||||||
"typescript": "4.6.4"
|
"typescript": "4.6.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -1075,7 +1076,7 @@
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/mdast": {
|
"node_modules/@types/mdast": {
|
||||||
|
@ -2883,6 +2884,18 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-import/node_modules/tsconfig-paths": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/json5": "^0.0.29",
|
||||||
|
"json5": "^1.0.1",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-prettier": {
|
"node_modules/eslint-plugin-prettier": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
|
||||||
|
@ -7153,15 +7166,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.14.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz",
|
||||||
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
"integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/json5": "^0.0.29",
|
"json5": "^2.2.1",
|
||||||
"json5": "^1.0.1",
|
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"strip-bom": "^3.0.0"
|
"strip-bom": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"json5": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
|
@ -8550,7 +8577,7 @@
|
||||||
"@types/json5": {
|
"@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/mdast": {
|
"@types/mdast": {
|
||||||
|
@ -10025,6 +10052,18 @@
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tsconfig-paths": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/json5": "^0.0.29",
|
||||||
|
"json5": "^1.0.1",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12975,15 +13014,22 @@
|
||||||
"integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw=="
|
"integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw=="
|
||||||
},
|
},
|
||||||
"tsconfig-paths": {
|
"tsconfig-paths": {
|
||||||
"version": "3.14.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz",
|
||||||
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
"integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/json5": "^0.0.29",
|
"json5": "^2.2.1",
|
||||||
"json5": "^1.0.1",
|
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"strip-bom": "^3.0.0"
|
"strip-bom": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"json5": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "npm run build:images && npm run build:web && npm run build:calendar",
|
"build": "npm run build:images && npm run build:web && npm run build:calendar && npm run build:api",
|
||||||
"build:images": "ts-node ./scripts/optimize-images",
|
|
||||||
"build:web": "next build",
|
"build:web": "next build",
|
||||||
"build:calendar": "ts-node ./scripts/generate-calendar",
|
"build:images": "ts-node -r tsconfig-paths/register ./scripts/optimize-images",
|
||||||
|
"build:calendar": "ts-node -r tsconfig-paths/register ./scripts/generate-calendar",
|
||||||
|
"build:api": "ts-node -r tsconfig-paths/register ./scripts/api/members",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"export": "next export",
|
"export": "next export",
|
||||||
"lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet",
|
"lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet",
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
"postcss-flexbugs-fixes": "^5.0.2",
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
"postcss-preset-env": "^7.0.0",
|
"postcss-preset-env": "^7.0.0",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
|
"tsconfig-paths": "^4.1.0",
|
||||||
"typescript": "4.6.4"
|
"typescript": "4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,14 @@ import React from "react";
|
||||||
import { Link } from "@/components/Link";
|
import { Link } from "@/components/Link";
|
||||||
import { Table } from "@/components/Table";
|
import { Table } from "@/components/Table";
|
||||||
import { Title } from "@/components/Title";
|
import { Title } from "@/components/Title";
|
||||||
import { getCurrentTerm } from "@/lib/events";
|
|
||||||
import { getMembers, Member } from "@/lib/members";
|
import { getMembers, Member } from "@/lib/members";
|
||||||
import { Term, capitalize } from "@/utils";
|
import { Term, capitalize, getCurrentTermYear } from "@/utils";
|
||||||
|
|
||||||
import styles from "./members.module.css";
|
import styles from "./members.module.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
members: Member[];
|
members: Member[];
|
||||||
year: string;
|
year: number;
|
||||||
term: Term;
|
term: Term;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ export default function Members(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||||
const curTerm = getCurrentTerm();
|
const curTerm = getCurrentTermYear();
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
year: curTerm.year,
|
year: curTerm.year,
|
||||||
|
|
|
@ -10,11 +10,11 @@ import { TeamMember } from "@/components/TeamMember";
|
||||||
import { TeamMemberCard } from "@/components/TeamMemberCard";
|
import { TeamMemberCard } from "@/components/TeamMemberCard";
|
||||||
import { Title } from "@/components/Title";
|
import { Title } from "@/components/Title";
|
||||||
import {
|
import {
|
||||||
getExec,
|
getExecs,
|
||||||
getExecNamePosPairs,
|
Metadata as TeamMemberData,
|
||||||
Metadata,
|
|
||||||
getMemberImagePath,
|
getMemberImagePath,
|
||||||
} from "@/lib/team";
|
} from "@/lib/team";
|
||||||
|
import { getCurrentTermYear } from "@/utils";
|
||||||
|
|
||||||
import designData from "../../content/team/design-team.json";
|
import designData from "../../content/team/design-team.json";
|
||||||
import discordData from "../../content/team/discord-team.json";
|
import discordData from "../../content/team/discord-team.json";
|
||||||
|
@ -31,88 +31,21 @@ import styles from "./team.module.css";
|
||||||
|
|
||||||
interface SerializedExec {
|
interface SerializedExec {
|
||||||
content: MDXRemoteSerializeResult;
|
content: MDXRemoteSerializeResult;
|
||||||
metadata: Metadata;
|
metadata: TeamMemberData;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Team {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
members: TeamMemberData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
execs: SerializedExec[];
|
execs: SerializedExec[];
|
||||||
design: Metadata[];
|
teams: Team[];
|
||||||
discord: Metadata[];
|
|
||||||
events: Metadata[];
|
|
||||||
external: Metadata[];
|
|
||||||
marketing: Metadata[];
|
|
||||||
photography: Metadata[];
|
|
||||||
representatives: Metadata[];
|
|
||||||
website: Metadata[];
|
|
||||||
systems: Metadata[];
|
|
||||||
terminal: Metadata[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Team({
|
export default function Team({ execs, teams }: Props) {
|
||||||
execs,
|
|
||||||
design,
|
|
||||||
discord,
|
|
||||||
events,
|
|
||||||
external,
|
|
||||||
marketing,
|
|
||||||
photography,
|
|
||||||
representatives,
|
|
||||||
website,
|
|
||||||
systems,
|
|
||||||
terminal,
|
|
||||||
}: Props) {
|
|
||||||
const teams = [
|
|
||||||
{
|
|
||||||
id: "reps",
|
|
||||||
name: "Community Representatives",
|
|
||||||
members: representatives,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "design",
|
|
||||||
name: "Design Team",
|
|
||||||
members: design,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "discord",
|
|
||||||
name: "Discord Team",
|
|
||||||
members: discord,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "events",
|
|
||||||
name: "Events Team",
|
|
||||||
members: events,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "external",
|
|
||||||
name: "External Affairs Team",
|
|
||||||
members: external,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "marketing",
|
|
||||||
name: "Marketing Team",
|
|
||||||
members: marketing,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "photography",
|
|
||||||
name: "Photography Team",
|
|
||||||
members: photography,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "website",
|
|
||||||
name: "Web Committee",
|
|
||||||
members: website,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "system",
|
|
||||||
name: "Systems Committee",
|
|
||||||
members: systems,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "terminal",
|
|
||||||
name: "Terminal Committee",
|
|
||||||
members: terminal,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>Team</Title>
|
<Title>Team</Title>
|
||||||
|
@ -122,16 +55,11 @@ export default function Team({
|
||||||
<h1 className={styles.header}>Meet the Team!</h1>
|
<h1 className={styles.header}>Meet the Team!</h1>
|
||||||
<div className={styles.nav}>
|
<div className={styles.nav}>
|
||||||
<Link href="#execs">The Executives</Link>
|
<Link href="#execs">The Executives</Link>
|
||||||
<Link href="#reps">Community Representatives</Link>
|
{teams.map((team) => (
|
||||||
<Link href="#design">Design</Link>
|
<Link href={`#${team.id}`} key={team.id}>
|
||||||
<Link href="#discord">Discord</Link>
|
{team.name}
|
||||||
<Link href="#events">Events</Link>
|
</Link>
|
||||||
<Link href="#external">External Affairs</Link>
|
))}
|
||||||
<Link href="#marketing">Marketing</Link>
|
|
||||||
<Link href="#photography">Photography</Link>
|
|
||||||
<Link href="#website">Web Committee</Link>
|
|
||||||
<Link href="#system">Systems Committee</Link>
|
|
||||||
<Link href="#terminal">Terminal Committee</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<h2
|
<h2
|
||||||
className={styles.subheading}
|
className={styles.subheading}
|
||||||
|
@ -180,7 +108,7 @@ Team.Layout = function TeamLayout(props: { children: React.ReactNode }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface MembersProps {
|
interface MembersProps {
|
||||||
team: Metadata[];
|
team: TeamMemberData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function MembersList(props: MembersProps) {
|
function MembersList(props: MembersProps) {
|
||||||
|
@ -193,93 +121,104 @@ function MembersList(props: MembersProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type TeamMember = Omit<Metadata, "image"> & { image?: string };
|
async function getTeamWithImages(team: Team): Promise<Team> {
|
||||||
|
return {
|
||||||
async function getTeamWithImages(team: TeamMember[]) {
|
...team,
|
||||||
return await Promise.all(
|
members: await Promise.all(
|
||||||
team.map(async (member) => {
|
team.members.map(async (member) => {
|
||||||
const image = member.image ?? (await getMemberImagePath(member.name));
|
const image = member.image ?? (await getMemberImagePath(member.name));
|
||||||
return {
|
return {
|
||||||
...member,
|
...member,
|
||||||
image,
|
image,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function memberComparer(a: Metadata, b: Metadata) {
|
function memberComparer(a: TeamMemberData, b: TeamMemberData) {
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortTeam(team: Metadata[]): Metadata[] {
|
function sortTeamMembers(team: Team): Team {
|
||||||
const leads = team
|
const leads = team.members
|
||||||
.filter(({ role }) => role === "Team Lead")
|
.filter(({ role }) => role === "Team Lead")
|
||||||
.sort(memberComparer);
|
.sort(memberComparer);
|
||||||
const general = team.filter(({ role }) => !role).sort(memberComparer);
|
|
||||||
const others = team
|
const general = team.members.filter(({ role }) => !role).sort(memberComparer);
|
||||||
|
|
||||||
|
const others = team.members
|
||||||
.filter(({ role }) => !!role && role !== "Team Lead")
|
.filter(({ role }) => !!role && role !== "Team Lead")
|
||||||
.sort(memberComparer);
|
.sort(memberComparer);
|
||||||
|
|
||||||
return [...leads, ...general, ...others];
|
return { ...team, members: [...leads, ...general, ...others] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||||
const execNamePosPairs = await getExecNamePosPairs();
|
const execs = await getExecs(getCurrentTermYear());
|
||||||
|
|
||||||
const execs = (await Promise.all(
|
// Note that rawTeams do not contain image paths of members, even though
|
||||||
execNamePosPairs.map((namePosPair) =>
|
// TypeScript thinks that it does. It's just to simplify some code.
|
||||||
getExec(namePosPair[0], namePosPair[1])
|
const rawTeams = [
|
||||||
)
|
{
|
||||||
)) as SerializedExec[];
|
id: "reps",
|
||||||
|
name: "Community Representatives",
|
||||||
|
members: repsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "design",
|
||||||
|
name: "Design",
|
||||||
|
members: designData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "discord",
|
||||||
|
name: "Discord",
|
||||||
|
members: discordData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "events",
|
||||||
|
name: "Events",
|
||||||
|
members: eventsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "external",
|
||||||
|
name: "External Affairs",
|
||||||
|
members: externalData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "marketing",
|
||||||
|
name: "Marketing",
|
||||||
|
members: marketingData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "photography",
|
||||||
|
name: "Photography",
|
||||||
|
members: photographyData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "website",
|
||||||
|
name: "Web Committee",
|
||||||
|
members: webData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "system",
|
||||||
|
name: "Systems Committee",
|
||||||
|
members: systemsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "terminal",
|
||||||
|
name: "Terminal Committee",
|
||||||
|
members: terminalData,
|
||||||
|
},
|
||||||
|
] as Team[];
|
||||||
|
|
||||||
let [
|
const teamsWithImages = await Promise.all(rawTeams.map(getTeamWithImages));
|
||||||
design,
|
const teamsAfterSorting = teamsWithImages.map(sortTeamMembers);
|
||||||
discord,
|
|
||||||
events,
|
|
||||||
external,
|
|
||||||
marketing,
|
|
||||||
photography,
|
|
||||||
representatives,
|
|
||||||
website,
|
|
||||||
systems,
|
|
||||||
terminal,
|
|
||||||
] = await Promise.all([
|
|
||||||
getTeamWithImages(designData),
|
|
||||||
getTeamWithImages(discordData),
|
|
||||||
getTeamWithImages(eventsData),
|
|
||||||
getTeamWithImages(externalData),
|
|
||||||
getTeamWithImages(marketingData),
|
|
||||||
getTeamWithImages(photographyData),
|
|
||||||
getTeamWithImages(repsData),
|
|
||||||
getTeamWithImages(webData),
|
|
||||||
getTeamWithImages(systemsData),
|
|
||||||
getTeamWithImages(terminalData),
|
|
||||||
]);
|
|
||||||
|
|
||||||
design = sortTeam(design);
|
|
||||||
discord = sortTeam(discord);
|
|
||||||
events = sortTeam(events);
|
|
||||||
external = sortTeam(external);
|
|
||||||
marketing = sortTeam(marketing);
|
|
||||||
representatives = sortTeam(representatives);
|
|
||||||
photography = sortTeam(photography);
|
|
||||||
website = sortTeam(website);
|
|
||||||
systems = sortTeam(systems);
|
|
||||||
terminal = sortTeam(terminal);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
execs,
|
execs,
|
||||||
design,
|
teams: teamsAfterSorting,
|
||||||
discord,
|
|
||||||
events,
|
|
||||||
external,
|
|
||||||
marketing,
|
|
||||||
photography,
|
|
||||||
representatives,
|
|
||||||
website,
|
|
||||||
systems,
|
|
||||||
terminal,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -63,7 +63,11 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const { year, term, event } = context.params!;
|
const { year, term, event } = context.params!;
|
||||||
return {
|
return {
|
||||||
props: { year, term, event: await getEventBySlug(year, term, event) },
|
props: {
|
||||||
|
year,
|
||||||
|
term,
|
||||||
|
event: await getEventBySlug(parseInt(year), term, event),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,7 +81,7 @@ export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
termsInYear.map(async (term) => {
|
termsInYear.map(async (term) => {
|
||||||
const eventsInTerm = await getEventsByTerm(year, term);
|
const eventsInTerm = await getEventsByTerm(year, term);
|
||||||
return eventsInTerm.map((event) => ({
|
return eventsInTerm.map((event) => ({
|
||||||
year,
|
year: year.toString(),
|
||||||
term,
|
term,
|
||||||
event,
|
event,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -10,22 +10,22 @@ import { MiniEventCard } from "@/components/MiniEventCard";
|
||||||
import { Title } from "@/components/Title";
|
import { Title } from "@/components/Title";
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
getEventsPageProps,
|
|
||||||
getEventYears,
|
getEventYears,
|
||||||
getEventTermsByYear,
|
getEventTermsByYear,
|
||||||
|
getEventsPageProps,
|
||||||
} from "@/lib/events";
|
} from "@/lib/events";
|
||||||
import { capitalize, Term } from "@/utils";
|
import { capitalize, Term, TermYear } from "@/utils";
|
||||||
|
|
||||||
import styles from "./index.module.css";
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
year: string;
|
year: number;
|
||||||
term: Term;
|
term: Term;
|
||||||
pastEvents: Event[];
|
pastEvents: Event[];
|
||||||
futureEvents: Event[];
|
futureEvents: Event[];
|
||||||
isCurrentTerm: boolean;
|
isCurrentTerm: boolean;
|
||||||
pastTerms: { year: string; term: Term }[];
|
pastTerms: TermYear[];
|
||||||
futureTerms: { year: string; term: Term }[];
|
futureTerms: TermYear[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TermPage(props: Props) {
|
export default function TermPage(props: Props) {
|
||||||
|
@ -61,7 +61,7 @@ export default function TermPage(props: Props) {
|
||||||
<HeaderLink
|
<HeaderLink
|
||||||
{...link}
|
{...link}
|
||||||
isCurrentTerm={link.year === props.year && link.term === props.term}
|
isCurrentTerm={link.year === props.year && link.term === props.term}
|
||||||
key={link.year + link.term}
|
key={`${link.year}${link.term}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Link href="/events/archive">Archive</Link>
|
<Link href="/events/archive">Archive</Link>
|
||||||
|
@ -129,7 +129,7 @@ export default function TermPage(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderLink(props: {
|
function HeaderLink(props: {
|
||||||
year: string;
|
year: number;
|
||||||
term: Term;
|
term: Term;
|
||||||
isCurrentTerm?: boolean;
|
isCurrentTerm?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
@ -151,7 +151,14 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
||||||
context
|
context
|
||||||
) => {
|
) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
return { props: await getEventsPageProps(context.params!) };
|
const params = context.params!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: await getEventsPageProps({
|
||||||
|
year: parseInt(params.year),
|
||||||
|
term: params.term,
|
||||||
|
}),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
|
@ -161,7 +168,7 @@ export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
years.map(async (year) => {
|
years.map(async (year) => {
|
||||||
const terms = await getEventTermsByYear(year);
|
const terms = await getEventTermsByYear(year);
|
||||||
return terms.map((curTerm) => ({
|
return terms.map((curTerm) => ({
|
||||||
params: { year: year, term: curTerm },
|
params: { year: year.toString(), term: curTerm },
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,7 +47,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
year: year,
|
year: year,
|
||||||
terms: await getEventTermsByYear(year),
|
terms: await getEventTermsByYear(parseInt(year)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -55,7 +55,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
||||||
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
const years = await getEventYears();
|
const years = await getEventYears();
|
||||||
const paths = years.map((curYear) => ({
|
const paths = years.map((curYear) => ({
|
||||||
params: { year: curYear },
|
params: { year: curYear.toString() },
|
||||||
}));
|
}));
|
||||||
return {
|
return {
|
||||||
paths: paths,
|
paths: paths,
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||||
const years = (await getEventYears()).reverse();
|
const years = (await getEventYears()).reverse();
|
||||||
const yearsWithTerms = await Promise.all(
|
const yearsWithTerms = await Promise.all(
|
||||||
years.map(async (year) => ({
|
years.map(async (year) => ({
|
||||||
year,
|
year: year.toString(),
|
||||||
terms: (await getEventTermsByYear(year)).reverse(),
|
terms: (await getEventTermsByYear(year)).reverse(),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { GetStaticProps } from "next";
|
import { GetStaticProps } from "next";
|
||||||
|
|
||||||
import { getCurrentTerm, getEventsPageProps } from "@/lib/events";
|
import { getEventsPageProps } from "@/lib/events";
|
||||||
|
import { getCurrentTermYear } from "@/utils";
|
||||||
|
|
||||||
import TermPage, { Props } from "./[year]/[term]";
|
import TermPage, { Props } from "./[year]/[term]";
|
||||||
|
|
||||||
export default TermPage;
|
export default TermPage;
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||||
return { props: await getEventsPageProps(getCurrentTerm()) };
|
return { props: await getEventsPageProps(getCurrentTermYear()) };
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Member list",
|
||||||
|
"description": "List of all current members of the Computer Science Club of the University of Waterloo",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"members": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"program": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { writeFile } from "fs/promises";
|
||||||
|
|
||||||
|
import { getMembers } from "@/lib/members";
|
||||||
|
import { getCurrentTermYear } from "@/utils";
|
||||||
|
|
||||||
|
async function createMembersApi() {
|
||||||
|
const { term, year } = getCurrentTermYear();
|
||||||
|
const members = await getMembers(year, term);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
$id: "schema/members.json",
|
||||||
|
members,
|
||||||
|
};
|
||||||
|
|
||||||
|
await writeFile("public/api/members.json", JSON.stringify(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void createMembersApi();
|
|
@ -1,60 +0,0 @@
|
||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
import { format } from "date-fns";
|
|
||||||
|
|
||||||
import { DATE_FORMAT } from "@/utils";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Note:
|
|
||||||
This script will not work for events by default anymore, since events now have startDate instead of endDate
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
getEventsByTerm,
|
|
||||||
getEventTermsByYear,
|
|
||||||
getEventYears,
|
|
||||||
} from "../lib/events";
|
|
||||||
import {
|
|
||||||
getNewsByTerm,
|
|
||||||
getNewsTermsByYear,
|
|
||||||
getNewsYears,
|
|
||||||
NEWS_PATH,
|
|
||||||
} from "../lib/news";
|
|
||||||
const EVENTS_PATH = path.join("content", "events");
|
|
||||||
|
|
||||||
export async function main() {
|
|
||||||
for (const year of await getEventYears()) {
|
|
||||||
for (const term of await getEventTermsByYear(year)) {
|
|
||||||
for (const slug of await getEventsByTerm(year, term)) {
|
|
||||||
const filePath = path.join(EVENTS_PATH, year, term, `${slug}.md`);
|
|
||||||
const file = await fs.readFile(filePath, "utf-8");
|
|
||||||
|
|
||||||
await fs.writeFile(filePath, replaceDate(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const year of await getNewsYears()) {
|
|
||||||
for (const term of await getNewsTermsByYear(year)) {
|
|
||||||
for (const slug of await getNewsByTerm(year, term)) {
|
|
||||||
const filePath = path.join(NEWS_PATH, year, term, `${slug}.md`);
|
|
||||||
const file = await fs.readFile(filePath, "utf-8");
|
|
||||||
|
|
||||||
await fs.writeFile(filePath, replaceDate(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceDate(file: string) {
|
|
||||||
const lines = file.split("\n");
|
|
||||||
const dateLineIdx = lines.findIndex((line) => line.startsWith("date: "));
|
|
||||||
const dateLine = lines[dateLineIdx];
|
|
||||||
const date = new Date(dateLine.slice("date: ".length + 1, -1));
|
|
||||||
|
|
||||||
lines[dateLineIdx] = `date: '${format(date, DATE_FORMAT)}'`;
|
|
||||||
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void main();
|
|
|
@ -4,7 +4,7 @@ import path from "path";
|
||||||
import { addHours } from "date-fns";
|
import { addHours } from "date-fns";
|
||||||
import ical, { ICalCalendarMethod } from "ical-generator";
|
import ical, { ICalCalendarMethod } from "ical-generator";
|
||||||
|
|
||||||
import { getAllEvents } from "../lib/events";
|
import { getAllEvents } from "@/lib/events";
|
||||||
|
|
||||||
export async function generateCalendar() {
|
export async function generateCalendar() {
|
||||||
const events = await getAllEvents();
|
const events = await getAllEvents();
|
||||||
|
|
65
utils.ts
65
utils.ts
|
@ -21,3 +21,68 @@ export function getLocalDateFromEST(date: Date): Date {
|
||||||
Intl.DateTimeFormat().resolvedOptions().timeZone
|
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TermYear {
|
||||||
|
term: Term;
|
||||||
|
year: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetTermYearOptions {
|
||||||
|
goBackwards?: boolean;
|
||||||
|
skipCurrent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* getTermYear(
|
||||||
|
start?: number | TermYear,
|
||||||
|
{ goBackwards = false, skipCurrent = false }: GetTermYearOptions = {}
|
||||||
|
) {
|
||||||
|
const allTerms = [...TERMS];
|
||||||
|
|
||||||
|
if (goBackwards) {
|
||||||
|
allTerms.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const todayYear = today.getFullYear();
|
||||||
|
const todayTerm = TERMS[Math.trunc(today.getMonth() / 4)];
|
||||||
|
|
||||||
|
start ??= { term: todayTerm, year: todayYear };
|
||||||
|
|
||||||
|
if (typeof start === "number") {
|
||||||
|
start = { term: allTerms[0], year: start };
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentYear = start.year;
|
||||||
|
while (0 <= currentYear && currentYear <= Number.MAX_SAFE_INTEGER) {
|
||||||
|
for (const currentTerm of allTerms) {
|
||||||
|
if (
|
||||||
|
currentYear === start.year &&
|
||||||
|
allTerms.indexOf(currentTerm) < allTerms.indexOf(start.term)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
skipCurrent &&
|
||||||
|
currentYear === start.year &&
|
||||||
|
currentTerm === start.term
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield { term: currentTerm, year: currentYear };
|
||||||
|
}
|
||||||
|
|
||||||
|
currentYear = currentYear + (goBackwards ? -1 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentTermYear() {
|
||||||
|
const result = getTermYear().next();
|
||||||
|
|
||||||
|
if (result.done === true) {
|
||||||
|
throw new Error("Cannot get current term. Iterator is done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue