Update Meet The Team page for Winter 2022 (Closes #382) #388
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
import { capitalize } from "@/utils";
|
||||
import { capitalize, Term } from "@/utils";
|
||||
|
||||
import { Link } from "./Link";
|
||||
import {
|
||||
|
@ -16,7 +16,7 @@ export interface Props {
|
|||
type: "news" | "events";
|
||||
items: {
|
||||
year: string;
|
||||
terms: string[];
|
||||
terms: Term[];
|
||||
}[];
|
||||
}
|
||||
|
||||
|
|
|
@ -75,36 +75,50 @@
|
|||
|
||||
.popupBackground {
|
||||
position: fixed;
|
||||
z-index: 11;
|
||||
background-color: var(--navbar-page-overlay);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 11;
|
||||
|
||||
background-color: var(--navbar-page-overlay);
|
||||
animation: revealBg 0.2s forwards;
|
||||
}
|
||||
|
||||
.popupContainer {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
z-index: 12;
|
||||
flex-direction: column;
|
||||
background-color: var(--secondary-background);
|
||||
padding: calc(20rem / 16) calc(40rem / 16);
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
z-index: 12;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: calc(40rem / 16);
|
||||
max-height: 75vh;
|
||||
overflow: auto;
|
||||
|
||||
background-color: var(--secondary-background);
|
||||
animation: popup 0.7s forwards;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
position: absolute;
|
||||
align-self: flex-end;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
/* reset default button styling */
|
||||
width: min-content;
|
||||
background: transparent;
|
||||
border: 0px solid transparent;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.popupContent {
|
||||
|
@ -113,25 +127,27 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.popupName {
|
||||
color: var(--primary-accent);
|
||||
margin: calc(24rem / 16) 0 0 0;
|
||||
.popupContent .name {
|
||||
margin-top: calc(24rem / 16);
|
||||
font-size: calc(18rem / 16);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.popupRole {
|
||||
color: var(--primary-heading);
|
||||
margin: 0 0 1rem 0;
|
||||
text-align: center;
|
||||
.popupContent .role {
|
||||
margin-bottom: calc(16rem / 16);
|
||||
font-size: calc(18rem / 16);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.popupDescription {
|
||||
.popupContent .description {
|
||||
display: block;
|
||||
font-size: calc(14rem / 16);
|
||||
}
|
||||
|
||||
.popupContent .description > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: calc(768rem / 16)) {
|
||||
.card {
|
||||
display: flex;
|
||||
|
|
|
@ -13,35 +13,6 @@ export interface TeamMemberCardProps {
|
|||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TeamMemberInfoProps extends TeamMemberCardProps {
|
||||
isPopup?: boolean;
|
||||
}
|
||||
|
||||
function TeamMemberInfo({
|
||||
name,
|
||||
role,
|
||||
image,
|
||||
children,
|
||||
isPopup = false,
|
||||
}: TeamMemberInfoProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.picture}>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={image}
|
||||
alt={`Picture of ${name}`}
|
||||
/>
|
||||
</div>
|
||||
<h1 className={isPopup ? styles.popupName : styles.name}>{name}</h1>
|
||||
<h2 className={isPopup ? styles.popupRole : styles.role}>{role}</h2>
|
||||
<div className={isPopup ? styles.popupDescription : styles.description}>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function TeamMemberCard({
|
||||
name,
|
||||
role,
|
||||
|
@ -50,11 +21,13 @@ export function TeamMemberCard({
|
|||
}: TeamMemberCardProps) {
|
||||
const { width } = useWindowDimension();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (isOpen || width <= 768) {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<article className={styles.card} onClick={handleClick}>
|
||||
|
@ -75,11 +48,28 @@ export function TeamMemberCard({
|
|||
);
|
||||
}
|
||||
|
||||
interface Propup extends TeamMemberCardProps {
|
||||
function TeamMemberInfo({ name, role, image, children }: TeamMemberCardProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.picture}>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={image}
|
||||
alt={`Picture of ${name}`}
|
||||
/>
|
||||
</div>
|
||||
<h1 className={styles.name}>{name}</h1>
|
||||
<h2 className={styles.role}>{role}</h2>
|
||||
<div className={styles.description}>{children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface PopupProps extends TeamMemberCardProps {
|
||||
handleClick: () => void;
|
||||
}
|
||||
|
||||
function ExecPopup({ name, role, image, children, handleClick }: Propup) {
|
||||
function ExecPopup({ name, role, image, children, handleClick }: PopupProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.popupBackground} onClick={handleClick} />
|
||||
|
@ -88,9 +78,7 @@ function ExecPopup({ name, role, image, children, handleClick }: Propup) {
|
|||
<Image src="images/team/popup-close.svg" />
|
||||
</button>
|
||||
<div className={styles.popupContent}>
|
||||
<TeamMemberInfo {...{ name, role, image }} isPopup={true}>
|
||||
{children}
|
||||
</TeamMemberInfo>
|
||||
<TeamMemberInfo {...{ name, role, image }}>{children}</TeamMemberInfo>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
name: 'Bootcamp: Mock Interviews'
|
||||
short: 'Come to practice your interview skills with experienced mentors!'
|
||||
date: 'January 23 2022 18:00'
|
||||
online: true
|
||||
location: 'Discord'
|
||||
poster: 'images/events/2022/winter/Bootcamp-resume-review-mock-interview.png'
|
||||
registerLink: https://lnkd.in/eHrs7_Fi
|
||||
---
|
||||
|
||||
📢 Mentee applications for Bootcamp are now OPEN! 📢 CSC is bringing back Bootcamp to gear you up for your next recruiting season, partnered with the UW Tech clubs! 💻 The drop-in mock interview event takes place January 23rd 6:00 - 10:00 PM EST.
|
||||
|
||||
💁♀️ Sign up as a mentee, and join our experienced mentors in Mock Interviews (virtual 1:1 sessions) to receive feedback from various tech backgrounds 📃 We’ll be using an SE22 FYDP project called ReviewKit. You will be paired with a mentor who is knowledgeable in the same or a similar career path to yours to ensure relevant feedback! 👌
|
||||
|
||||
A mentor will be paired with you based on your career interests to provide insightful feedback and advice to rock your job search - don’t miss out! If you’re interested, please sign up! We would love to help you feel ready and confident for the upcoming job hunt.
|
||||
|
||||
📝After signing up, you’ll soon receive a link to the Discord server in which this event takes place. Our collaborating clubs are excited to bring you this opportunity to sharpen your job hunting skills
|
||||
|
||||
👉 If you’re interested, please apply at https://lnkd.in/eHrs7_Fi
|
||||
|
||||
Alternatively, you can email us at exec@csclub.uwaterloo.ca with the year and program you’re in, along with interested job paths.
|
||||
|
||||
📅 Deadline to Apply: January 16th, 2022, 11:59AM EST
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
name: 'Bootcamp: Resume Reviews'
|
||||
short: 'Come to receive insightful feedback and advice on your resume to rock your job search.'
|
||||
date: 'January 16 2022 18:00'
|
||||
online: true
|
||||
location: 'Discord'
|
||||
poster: 'images/events/2022/winter/Bootcamp-resume-review-mock-interview.png'
|
||||
registerLink: https://lnkd.in/eHrs7_Fi
|
||||
---
|
||||
|
||||
📢 Mentee applications for Bootcamp are now OPEN! 📢 CSC is bringing back Bootcamp to gear you up for your next recruiting season, partnered with the UW Tech clubs! 💻 The drop-in resume review event takes place January 16th 6:00 - 10:00 PM EST.
|
||||
|
||||
💁♀️ Sign up as a mentee, and join our experienced mentors in Resume Reviews (virtual 1:1 sessions) to receive feedback from various tech backgrounds 📃 We’ll be using an SE22 FYDP project called ReviewKit. You will be paired with a mentor who is knowledgeable in the same or a similar career path to yours to ensure relevant feedback! 👌
|
||||
|
||||
A mentor will be paired with you based on your career interests to provide insightful feedback and advice to rock your job search - don’t miss out! If you’re interested, please sign up! We would love to help you feel ready and confident for the upcoming job hunt.
|
||||
|
||||
📝After signing up, you’ll soon receive a link to the Discord server in which this event takes place. Our collaborating clubs are excited to bring you this opportunity to sharpen your job hunting skills
|
||||
|
||||
👉 If you’re interested, please apply at https://lnkd.in/eHrs7_Fi
|
||||
|
||||
Alternatively, you can email us at exec@csclub.uwaterloo.ca with the year and program you’re in, along with interested job paths.
|
||||
|
||||
📅 Deadline to Apply: January 16th, 2022, 11:59AM EST
|
Binary file not shown.
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
@ -8,9 +8,10 @@ import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
|||
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 { Term, TERMS, isTerm } from "../utils";
|
||||
|
||||
const EVENTS_PATH = path.join("content", "events");
|
||||
export const TERMS = ["winter", "spring", "fall"];
|
||||
|
||||
export async function getEventYears(): Promise<string[]> {
|
||||
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
|
||||
|
@ -19,12 +20,12 @@ export async function getEventYears(): Promise<string[]> {
|
|||
.sort();
|
||||
}
|
||||
|
||||
export async function getEventTermsByYear(year: string): Promise<string[]> {
|
||||
export async function getEventTermsByYear(year: string): Promise<Term[]> {
|
||||
return (
|
||||
await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true })
|
||||
)
|
||||
.filter((dirent) => dirent.isDirectory() && TERMS.includes(dirent.name))
|
||||
.map((dirent) => dirent.name)
|
||||
.filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
|
||||
.map((dirent) => dirent.name as Term)
|
||||
.sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b));
|
||||
}
|
||||
|
||||
|
@ -58,7 +59,7 @@ export const DATE_FORMAT = "MMMM dd yyyy HH:mm";
|
|||
|
||||
export async function getEventBySlug(
|
||||
year: string,
|
||||
term: string,
|
||||
term: Term,
|
||||
slug: string
|
||||
): Promise<Event> {
|
||||
const file = await fs.readFile(
|
||||
|
@ -84,7 +85,7 @@ export async function getEventBySlug(
|
|||
|
||||
export async function getEventsByTerm(
|
||||
year: string,
|
||||
term: string
|
||||
term: Term
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
return (await fs.readdir(path.join(EVENTS_PATH, year, term)))
|
||||
|
@ -130,7 +131,7 @@ export async function getUpcomingEvents(): Promise<Event[]> {
|
|||
});
|
||||
}
|
||||
|
||||
export async function getAllEvents() {
|
||||
export async function getAllEvents(): Promise<Event[]> {
|
||||
const events: Event[] = [];
|
||||
|
||||
for (const year of await getEventYears()) {
|
||||
|
@ -149,7 +150,7 @@ export async function getEventsPageProps({
|
|||
term,
|
||||
}: {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
}): Promise<Props> {
|
||||
const eventNames = await getEventsByTerm(year, term);
|
||||
|
||||
|
@ -177,7 +178,7 @@ export async function getEventsPageProps({
|
|||
const eventYears = await getEventYears();
|
||||
|
||||
const minYear = eventYears[0];
|
||||
const pastTerms: { year: string; term: string }[] = [];
|
||||
const pastTerms: { year: string; term: Term }[] = [];
|
||||
let curPastYear = year;
|
||||
let curPastTerm = term;
|
||||
while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) {
|
||||
|
@ -191,7 +192,7 @@ export async function getEventsPageProps({
|
|||
pastTerms.reverse();
|
||||
|
||||
const maxYear = eventYears[eventYears.length - 1];
|
||||
const futureTerms: { year: string; term: string }[] = [];
|
||||
const futureTerms: { year: string; term: Term }[] = [];
|
||||
let curFutureYear = year;
|
||||
let curFutureTerm = term;
|
||||
while (
|
||||
|
@ -217,7 +218,7 @@ export async function getEventsPageProps({
|
|||
};
|
||||
}
|
||||
|
||||
export function getCurrentTerm() {
|
||||
export function getCurrentTerm(): { year: string; term: Term } {
|
||||
const today = new Date().toLocaleDateString("en-CA", {
|
||||
timeZone: "EST",
|
||||
year: "numeric",
|
||||
|
@ -241,17 +242,14 @@ export function getCurrentTerm() {
|
|||
term = "fall";
|
||||
}
|
||||
|
||||
if (term === "") {
|
||||
if (!isTerm(term)) {
|
||||
throw new Error("Error setting the current term");
|
||||
}
|
||||
|
||||
return { year, term };
|
||||
}
|
||||
|
||||
function getPastTerm(
|
||||
year: string,
|
||||
term: string
|
||||
): { year: string; term: string } {
|
||||
function getPastTerm(year: string, term: Term): { year: string; term: Term } {
|
||||
const index = TERMS.indexOf(term);
|
||||
|
||||
if (index === -1) {
|
||||
|
@ -269,10 +267,7 @@ function getPastTerm(
|
|||
};
|
||||
}
|
||||
|
||||
function getFutureTerm(
|
||||
year: string,
|
||||
term: string
|
||||
): { year: string; term: string } {
|
||||
function getFutureTerm(year: string, term: Term): { year: string; term: Term } {
|
||||
const index = TERMS.indexOf(term);
|
||||
|
||||
if (index === -1) {
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
import { Client } from "ldapts";
|
||||
|
||||
import { Term } from "@/utils";
|
||||
|
||||
export interface Member {
|
||||
name: string;
|
||||
id: string;
|
||||
program: string;
|
||||
}
|
||||
|
||||
export async function getMembers(
|
||||
year: string,
|
||||
term: string
|
||||
): Promise<Member[]> {
|
||||
if (term !== "winter" && term !== "spring" && term !== "fall") {
|
||||
throw new Error(`[getMembers] Not a valid term: "${term}"`);
|
||||
}
|
||||
|
||||
export async function getMembers(year: string, term: Term): Promise<Member[]> {
|
||||
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
||||
return dummyMembers;
|
||||
}
|
||||
|
||||
let members: Member[] = [];
|
||||
|
||||
const url = "ldap://ldap1.csclub.uwaterloo.ca";
|
||||
|
@ -28,7 +22,10 @@ export async function getMembers(
|
|||
await client.bind("", "");
|
||||
const { searchEntries } = await client.search(searchDN, {
|
||||
scope: "sub",
|
||||
filter: `(&(objectClass=member)(term=${term.slice(0, 1)}${year}))`,
|
||||
filter: `(&(objectClass=member)(term=${(term as string).slice(
|
||||
0,
|
||||
1
|
||||
)}${year}))`,
|
||||
});
|
||||
|
||||
members = searchEntries
|
||||
|
@ -39,7 +36,9 @@ export async function getMembers(
|
|||
program: item.program === undefined ? "" : (item.program as string),
|
||||
};
|
||||
})
|
||||
.sort((item1: Member, item2: Member) => item1.id.localeCompare(item2.id));
|
||||
.sort((item1: Member, item2: Member) =>
|
||||
item1.name.localeCompare(item2.name)
|
||||
);
|
||||
} finally {
|
||||
await client.unbind();
|
||||
}
|
||||
|
|
13
lib/news.ts
13
lib/news.ts
|
@ -6,10 +6,11 @@ import matter from "gray-matter";
|
|||
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
|
||||
import { isTerm, Term, TERMS } from "@/utils";
|
||||
|
||||
import { DATE_FORMAT, getLocalDateFromEST } from "./events";
|
||||
|
||||
export const NEWS_PATH = path.join("content", "news");
|
||||
const TERMS = ["winter", "spring", "fall"];
|
||||
|
||||
export interface Metadata {
|
||||
author: string;
|
||||
|
@ -28,20 +29,20 @@ export async function getNewsYears(): Promise<string[]> {
|
|||
.sort();
|
||||
}
|
||||
|
||||
export async function getNewsTermsByYear(year: string): Promise<string[]> {
|
||||
export async function getNewsTermsByYear(year: string): Promise<Term[]> {
|
||||
return (
|
||||
await fs.readdir(path.join(NEWS_PATH, year), {
|
||||
withFileTypes: true,
|
||||
})
|
||||
)
|
||||
.filter((dirent) => dirent.isDirectory() && TERMS.includes(dirent.name))
|
||||
.map((dirent) => dirent.name)
|
||||
.filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
|
||||
.map((dirent) => dirent.name as Term)
|
||||
.sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b));
|
||||
}
|
||||
|
||||
export async function getNewsByTerm(
|
||||
year: string,
|
||||
term: string
|
||||
term: Term
|
||||
): Promise<string[]> {
|
||||
return (
|
||||
await fs.readdir(path.join(NEWS_PATH, year, term), {
|
||||
|
@ -54,7 +55,7 @@ export async function getNewsByTerm(
|
|||
|
||||
export async function getNewsBySlug(
|
||||
year: string,
|
||||
term: string,
|
||||
term: Term,
|
||||
slug: string
|
||||
): Promise<News> {
|
||||
const raw = await fs.readFile(
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@
|
|||
"private": true,
|
||||
"engines": {
|
||||
"node": "^16",
|
||||
"npm": "^7"
|
||||
"npm": "^8.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"dependencies": {
|
||||
"@mdx-js/loader": "^1.6.22",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@next/mdx": "11.0.1",
|
||||
"@next/mdx": "11.1.3",
|
||||
"@squoosh/lib": "^0.4.0",
|
||||
"date-fns": "^2.11.1",
|
||||
"date-fns-tz": "^1.1.6",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"postcss": "^8.3.0",
|
||||
"postcss-calc": "^8.0.0",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"postcss-preset-env": "^7.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "4.3.5"
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@ import { Table } from "@/components/Table";
|
|||
import { Title } from "@/components/Title";
|
||||
import { getCurrentTerm } from "@/lib/events";
|
||||
import { getMembers, Member } from "@/lib/members";
|
||||
import { capitalize } from "@/utils";
|
||||
import { Term, capitalize } from "@/utils";
|
||||
|
||||
import styles from "./members.module.css";
|
||||
|
||||
interface Props {
|
||||
members: Member[];
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
}
|
||||
|
||||
export default function Members(props: Props) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
getEventsByTerm,
|
||||
getEventBySlug,
|
||||
} from "@/lib/events";
|
||||
import { capitalize } from "@/utils";
|
||||
import { capitalize, Term } from "@/utils";
|
||||
|
||||
export default function EventInfoPage({ year, term, event }: Props) {
|
||||
return (
|
||||
|
@ -43,13 +43,13 @@ EventInfoPage.getShapesConfig = ((width, height) => {
|
|||
|
||||
interface Props {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
event: Event;
|
||||
}
|
||||
|
||||
interface Params extends ParsedUrlQuery {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
event: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,21 +14,21 @@ import {
|
|||
getEventYears,
|
||||
getEventTermsByYear,
|
||||
} from "@/lib/events";
|
||||
import { capitalize } from "@/utils";
|
||||
import { capitalize, Term } from "@/utils";
|
||||
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export interface Props {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
pastEvents: Event[];
|
||||
futureEvents: Event[];
|
||||
isCurrentTerm: boolean;
|
||||
pastTerms: { year: string; term: string }[];
|
||||
futureTerms: { year: string; term: string }[];
|
||||
pastTerms: { year: string; term: Term }[];
|
||||
futureTerms: { year: string; term: Term }[];
|
||||
}
|
||||
|
||||
export default function Term(props: Props) {
|
||||
export default function TermPage(props: Props) {
|
||||
let headerTerms = [{ year: props.year, term: props.term }];
|
||||
|
||||
// p, Current, f
|
||||
|
@ -120,7 +120,7 @@ export default function Term(props: Props) {
|
|||
|
||||
function HeaderLink(props: {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
isCurrentTerm?: boolean;
|
||||
}) {
|
||||
return (
|
||||
|
@ -134,7 +134,7 @@ function HeaderLink(props: {
|
|||
|
||||
interface Params extends ParsedUrlQuery {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props, Params> = async (
|
||||
|
|
|
@ -6,12 +6,13 @@ import React from "react";
|
|||
import { Link } from "@/components/Link";
|
||||
import { Title } from "@/components/Title";
|
||||
import { getEventYears, getEventTermsByYear } from "@/lib/events";
|
||||
import { Term } from "@/utils";
|
||||
|
||||
import styles from "./index.module.css";
|
||||
|
||||
interface Props {
|
||||
year: string;
|
||||
terms: string[];
|
||||
terms: Term[];
|
||||
}
|
||||
|
||||
export default function Year(props: Props) {
|
||||
|
|
|
@ -2,9 +2,9 @@ import { GetStaticProps } from "next";
|
|||
|
||||
import { getCurrentTerm, getEventsPageProps } from "@/lib/events";
|
||||
|
||||
import Term, { Props } from "./[year]/[term]";
|
||||
import TermPage, { Props } from "./[year]/[term]";
|
||||
|
||||
export default Term;
|
||||
export default TermPage;
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
return { props: await getEventsPageProps(getCurrentTerm()) };
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: calc(8rem / 16);
|
||||
}
|
||||
|
||||
.clubTitleWrapper img {
|
||||
|
@ -49,6 +50,7 @@
|
|||
text-align: center;
|
||||
white-space: nowrap;
|
||||
color: var(--primary-heading);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.clubDescription {
|
||||
|
|
|
@ -18,13 +18,13 @@ import {
|
|||
getNewsYears,
|
||||
News,
|
||||
} from "@/lib/news";
|
||||
import { capitalize } from "@/utils";
|
||||
import { capitalize, Term } from "@/utils";
|
||||
|
||||
import styles from "./[term].module.css";
|
||||
|
||||
interface Props {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
news: News[];
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
|||
|
||||
interface Params extends ParsedUrlQuery {
|
||||
year: string;
|
||||
term: string;
|
||||
term: Term;
|
||||
}
|
||||
|
||||
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||
|
|
8
utils.ts
8
utils.ts
|
@ -1,3 +1,11 @@
|
|||
export const TERMS = ["winter", "spring", "fall"] as const;
|
||||
export type Term = typeof TERMS[number];
|
||||
|
||||
// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
|
||||
export function isTerm(x: string): x is Term {
|
||||
return TERMS.some((term) => x === term);
|
||||
}
|
||||
|
||||
export function capitalize(str: string) {
|
||||
return str.slice(0, 1).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue