Includes mobile Team Member Card Closes #9 Closes #42 Co-authored-by: Bonnie <bonniepeng2002@gmail.com> Reviewed-on: #94 Reviewed-by: Aditya Thakral <a3thakra@csclub.uwaterloo.ca> Co-authored-by: b38peng <b38peng@uwaterloo.ca> Co-committed-by: b38peng <b38peng@uwaterloo.ca>
|
@ -4,9 +4,7 @@
|
|||
"eslint.codeActionsOnSave.mode": "all",
|
||||
"[css]": {
|
||||
"editor.suggest.insertMode": "replace",
|
||||
"gitlens.codeLens.scopes": [
|
||||
"document"
|
||||
],
|
||||
"gitlens.codeLens.scopes": ["document"],
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[javascript]": {
|
||||
|
@ -39,8 +37,9 @@
|
|||
"node_modules": true
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"files.eol": "\n",
|
||||
"[markdown]": {
|
||||
"editor.wordWrap": "on",
|
||||
"editor.quickSuggestions": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { DefaultLayout } from "./DefaultLayout";
|
|||
|
||||
import styles from "./Bubble.module.css";
|
||||
|
||||
export default function Bubble(props: { children: React.ReactNode }) {
|
||||
export function Bubble(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.bubble} aria-hidden>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
width: 100%;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.caption {
|
||||
|
@ -25,3 +26,15 @@
|
|||
font-weight: 600;
|
||||
color: var(--primary-heading);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: calc(768rem / 16)) {
|
||||
.img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.caption {
|
||||
text-align: center;
|
||||
font-size: calc(14rem / 16);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import styles from "./TeamMember.module.css";
|
|||
interface TeamMemberProps {
|
||||
name: string;
|
||||
role: string;
|
||||
image?: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export const TeamMember: React.FC<TeamMemberProps> = ({
|
||||
|
@ -17,11 +17,7 @@ export const TeamMember: React.FC<TeamMemberProps> = ({
|
|||
}) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Image
|
||||
className={styles.img}
|
||||
src={image ?? "/images/team-member-placeholder.svg"}
|
||||
alt={`Picture of ${name}`}
|
||||
/>
|
||||
<Image className={styles.img} src={image} alt={`Picture of ${name}`} />
|
||||
<div className={styles.caption}>
|
||||
<div className={styles.name}>{name}</div>
|
||||
<div className={styles.role}>{role}</div>
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
grid-area: role;
|
||||
margin: 0;
|
||||
|
||||
color: var(--primary-heading);
|
||||
font-size: calc(24rem / 16);
|
||||
line-height: calc(40 / 24);
|
||||
font-weight: 600;
|
||||
|
@ -51,39 +52,110 @@
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* TODO: Use the correct mobile styles from figma
|
||||
@media only screen and (max-width: calc(375rem / 16)) {
|
||||
.card {
|
||||
grid-template-columns: calc(70rem / 16) auto;
|
||||
grid-template-rows: auto calc(calc(14rem / 16) * 1.5 + calc(12rem / 16)) auto;
|
||||
grid-template-areas:
|
||||
"picture name"
|
||||
"picture role"
|
||||
"description description";
|
||||
column-gap: 1.4375rem;
|
||||
align-items: end;
|
||||
/* Popup */
|
||||
|
||||
max-width: calc(190rem / 16);
|
||||
@keyframes popup {
|
||||
0% {
|
||||
transform: scale(0.4) translate(0, -50%);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translate(0, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes revealBg {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.popupBackground {
|
||||
position: fixed;
|
||||
z-index: 11;
|
||||
background-color: var(--navbar-page-overlay);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
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;
|
||||
top: 50%;
|
||||
animation: popup 0.7s forwards;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
align-self: flex-end;
|
||||
/* reset default button styling */
|
||||
width: min-content;
|
||||
background: transparent;
|
||||
border: 0px solid transparent;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.popupContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popupImage {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.popupName {
|
||||
color: var(--primary-accent);
|
||||
margin: calc(24rem / 16) 0 0 0;
|
||||
font-size: calc(18rem / 16);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.popupRole {
|
||||
color: var(--primary-heading);
|
||||
margin: 0 0 1rem 0;
|
||||
text-align: center;
|
||||
font-size: calc(18rem / 16);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.popupDescription {
|
||||
font-size: calc(14rem / 16);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: calc(768rem / 16)) {
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: calc(135rem / 16);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.picture {
|
||||
max-width: calc(70rem / 16);
|
||||
max-height: calc(70rem / 16);
|
||||
.name {
|
||||
margin-top: calc(24rem / 16);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.name,
|
||||
.role,
|
||||
.description {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.role {
|
||||
margin-bottom: calc(12rem / 16);
|
||||
text-align: center;
|
||||
font-size: calc(14rem / 16);
|
||||
}
|
||||
|
||||
.description {
|
||||
justify-self: top;
|
||||
margin-top: calc(12rem / 16);
|
||||
margin-left: calc(12rem / 16);
|
||||
display: none;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
|
|
@ -1,29 +1,98 @@
|
|||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useWindowDimension } from "@/hooks/useWindowDimension";
|
||||
|
||||
import { Image } from "./Image";
|
||||
|
||||
import styles from "./TeamMemberCard.module.css";
|
||||
|
||||
interface TeamMemberCardProps {
|
||||
export interface TeamMemberCardProps {
|
||||
name: string;
|
||||
role: string;
|
||||
image?: string; // path to image of person, relative to public directory
|
||||
image: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TeamMemberCard(props: TeamMemberCardProps) {
|
||||
interface TeamMemberInfoProps extends TeamMemberCardProps {
|
||||
isPopup?: boolean;
|
||||
}
|
||||
|
||||
function TeamMemberInfo({
|
||||
name,
|
||||
role,
|
||||
image,
|
||||
children,
|
||||
isPopup = false,
|
||||
}: TeamMemberInfoProps) {
|
||||
return (
|
||||
<article className={styles.card}>
|
||||
<>
|
||||
<div className={styles.picture}>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={props.image ?? "/images/team-member-placeholder.svg"}
|
||||
alt={`Picture of ${props.name}`}
|
||||
className={isPopup ? styles.popupImage : styles.image}
|
||||
src={image}
|
||||
alt={`Picture of ${name}`}
|
||||
/>
|
||||
</div>
|
||||
<h1 className={styles.name}>{props.name}</h1>
|
||||
<h2 className={styles.role}>{props.role}</h2>
|
||||
<div className={styles.description}>{props.children}</div>
|
||||
</article>
|
||||
<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,
|
||||
image,
|
||||
children,
|
||||
}: TeamMemberCardProps) {
|
||||
const { width } = useWindowDimension();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const handleClick = () => {
|
||||
if (isOpen || width <= 768) {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<article className={styles.card} onClick={handleClick}>
|
||||
<TeamMemberInfo {...{ name, role, image }}>{children}</TeamMemberInfo>
|
||||
</article>
|
||||
|
||||
{isOpen && (
|
||||
<ExecPopup
|
||||
name={name}
|
||||
role={role}
|
||||
image={image}
|
||||
handleClick={handleClick}
|
||||
>
|
||||
{children}
|
||||
</ExecPopup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface Propup extends TeamMemberCardProps {
|
||||
handleClick: () => void;
|
||||
}
|
||||
|
||||
function ExecPopup({ name, role, image, children, handleClick }: Propup) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.popupBackground} onClick={handleClick} />
|
||||
<div className={styles.popupContainer}>
|
||||
<button className={styles.closeBtn} onClick={handleClick}>
|
||||
<Image src="images/team/popup-close.svg" />
|
||||
</button>
|
||||
<div className={styles.popupContent}>
|
||||
<TeamMemberInfo {...{ name, role, image }} isPopup={true}>
|
||||
{children}
|
||||
</TeamMemberInfo>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -166,16 +166,16 @@ export function TeamMemberDemo() {
|
|||
</div>
|
||||
<hr />
|
||||
<div className={styles.teamMembers}>
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
<TeamMember {...dogeMetadata} image="/images/playground/doge.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -184,7 +184,7 @@ export function TeamMemberDemo() {
|
|||
export function TeamMemberCardDemo() {
|
||||
return (
|
||||
<div className={styles.teamMemberCardDemo}>
|
||||
<TeamMemberCard {...codeyMetadata}>
|
||||
<TeamMemberCard {...codeyMetadata} image="/images/playground/doge.jpg">
|
||||
<CodeyInfo />
|
||||
</TeamMemberCard>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Bubble from "@/components/Bubble";
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
|
||||
<Bubble>
|
||||
|
||||
|
@ -17,13 +17,13 @@ growth.
|
|||
## Our <span>Vision</span>
|
||||
|
||||
1. Academic: Promoting the knowledge and interest of Computer Science, as well
|
||||
as supporting students throughout their academic experiences.
|
||||
as supporting students throughout their academic experiences.
|
||||
|
||||
2. Career: Providing career guidance and resources to help students gain
|
||||
experience and knowledge for their own job search.
|
||||
experience and knowledge for their own job search.
|
||||
|
||||
3. Community: Encouraging interpersonal relationships through community building
|
||||
and social events for all computing students.
|
||||
and social events for all computing students.
|
||||
|
||||
</Bubble>
|
||||
|
||||
|
@ -64,9 +64,9 @@ The CS Club office is located at room **MC 3036/3037**, in the Math & Computer
|
|||
Building of the University of Waterloo.
|
||||
|
||||
- An office favorite is our $0.50 pop for members. We have a fridge in the
|
||||
office which is stocked with many different kinds of pop.
|
||||
office which is stocked with many different kinds of pop.
|
||||
- We have lots of informative books, 5 computer terminals, and an array of
|
||||
knowledgeable people to talk to.
|
||||
knowledgeable people to talk to.
|
||||
|
||||
Come visit us on campus in our office! Meet new members and find out what's new
|
||||
in the club.
|
||||
|
@ -77,7 +77,7 @@ Computer Science Club <br />
|
|||
Math & Computer 3036/3037 <br />
|
||||
University of Waterloo <br />
|
||||
200 University Avenue West <br />
|
||||
Waterloo, ON N2L 3G1 <br />
|
||||
Waterloo, ON N2L 3G1 <br />
|
||||
Canada
|
||||
|
||||
Our office phone number is [(519) 888-4567 x33870](tel:+15198884567,33870)
|
||||
|
@ -85,5 +85,3 @@ Our office phone number is [(519) 888-4567 x33870](tel:+15198884567,33870)
|
|||
</address>
|
||||
|
||||
</Bubble>
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Kallen Tu
|
||||
role: President
|
||||
---
|
||||
|
||||
words words words codey words words words words codey words words words words codey words words words words codey words words words words codey words words words words words codey words words words words codey words words words words codey words words words words codey words words words
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Gordon Le
|
||||
role: Vice President
|
||||
---
|
||||
|
||||
words words words codey words words words words codey words words words words codey words words words words codey words words words words codey words words words words words codey words words words words codey words words words words codey words words words words codey words words words
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Nakul Vijhani
|
||||
role: Assistant Vice President
|
||||
---
|
||||
|
||||
words words words codey words words words words codey words words words words codey words words words words codey words words words words codey words words words words words codey words words words words codey words words words words codey words words words words codey words words words
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Neil Parikh
|
||||
role: Treasurer
|
||||
---
|
||||
|
||||
words words words codey words words words words codey words words words words codey words words words words codey words words words words codey words words words words words codey words words words words codey words words words words codey words words words words codey words words words
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Max Erenberg
|
||||
role: Systems Administrator
|
||||
---
|
||||
|
||||
words words words codey words words words words codey words words words words codey words words words words codey words words words words codey words words words words words codey words words words words codey words words words words codey words words words words codey words words words
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Codey
|
||||
role: Mascot
|
||||
---
|
||||
|
||||
The one, the only, Codey! Codey is ecstatic to be your mascot for this term. Codey loves programming and playing on their laptop. You can often find Codey posing for event promo graphics, or chilling in the CSC discord.
|
|
@ -0,0 +1,114 @@
|
|||
[
|
||||
{
|
||||
"name": "Brendan Wong",
|
||||
"role": "Designer"
|
||||
},
|
||||
{
|
||||
"name": "Kailin Chan",
|
||||
"role": "Designer"
|
||||
},
|
||||
{
|
||||
"name": "Karen Lee",
|
||||
"role": "Designer"
|
||||
},
|
||||
{
|
||||
"name": "Sam Honoridez",
|
||||
"role": "Designer"
|
||||
},
|
||||
{
|
||||
"name": "Anna Wang",
|
||||
"role": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Jason Sang",
|
||||
"role": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Ravindu Angammana",
|
||||
"role": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Shi Han",
|
||||
"role": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Stephanie Xu",
|
||||
"role": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Yanni Wang",
|
||||
"role": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Anjing Li",
|
||||
"role": "Marketing"
|
||||
},
|
||||
{
|
||||
"name": "Patrick He",
|
||||
"role": "Marketing"
|
||||
},
|
||||
{
|
||||
"name": "Richa Dalal",
|
||||
"role": "Marketing"
|
||||
},
|
||||
{
|
||||
"name": "Sherry Lev",
|
||||
"role": "Marketing"
|
||||
},
|
||||
{
|
||||
"name": "Alex Zhang",
|
||||
"role": "Discord Mod"
|
||||
},
|
||||
{
|
||||
"name": "Andrew Wang",
|
||||
"role": "Discord Mod"
|
||||
},
|
||||
{
|
||||
"name": "Charles Zhang",
|
||||
"role": "Discord Mod"
|
||||
},
|
||||
{
|
||||
"name": "Edwin Yang",
|
||||
"role": "Discord Mod"
|
||||
},
|
||||
{
|
||||
"name": "Mark Chen",
|
||||
"role": "Discord Mod"
|
||||
},
|
||||
{
|
||||
"name": "Aaron Choo",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Athena Liu",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Betty Guo",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Chris Xie",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Dora Su",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Eden Chan",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Felix Yang",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Guneet Bola",
|
||||
"role": "Representative"
|
||||
},
|
||||
{
|
||||
"name": "Juthika Hoque",
|
||||
"role": "Representative"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"name": "Max Erenberg",
|
||||
"role": "Admin"
|
||||
},
|
||||
{
|
||||
"name": "Andrew Wang",
|
||||
"role": "Member"
|
||||
},
|
||||
{
|
||||
"name": "Bill Xiang",
|
||||
"role": "Member"
|
||||
},
|
||||
{
|
||||
"name": "Raymond Li",
|
||||
"role": "Member"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,38 @@
|
|||
[
|
||||
{
|
||||
"name": "Aditya Thakral",
|
||||
"role": "Team Lead"
|
||||
},
|
||||
{
|
||||
"name": "Neil Parikh",
|
||||
"role": "Team Lead"
|
||||
},
|
||||
{
|
||||
"name": "Amy Wang",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Bonnie Peng",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Catherine Wan",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Dora Su",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Jared He",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Linna Luo",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "William Tran",
|
||||
"role": "Developer"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,37 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
interface WindowDimension {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function getWindowDimension() {
|
||||
const { innerWidth: width, innerHeight: height } = window;
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
export function useWindowDimension(): WindowDimension {
|
||||
const [windowSize, setWindowDimension] = useState<WindowDimension>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setWindowDimension(getWindowDimension());
|
||||
};
|
||||
|
||||
// Set size at the first client-side load
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return windowSize;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { readFile, readdir, access } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
import matter from "gray-matter";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
|
||||
const EXECS_PATH = path.join("content", "meet-the-team", "execs");
|
||||
const fileType = ".md";
|
||||
|
||||
export interface Metadata {
|
||||
name: string;
|
||||
role: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export async function getExecNames() {
|
||||
return (await readdir(EXECS_PATH))
|
||||
.filter((name) => name.endsWith(fileType))
|
||||
.map((name) => name.slice(0, -1 * fileType.length));
|
||||
}
|
||||
|
||||
export async function getExec(fileName: string, convert = true) {
|
||||
const raw = await readFile(path.join(EXECS_PATH, `${fileName}${fileType}`));
|
||||
const { content, data: metadata } = matter(raw);
|
||||
const image = await getMemberImagePath(metadata.name);
|
||||
|
||||
return {
|
||||
content: convert ? await serialize(content) : content,
|
||||
metadata: { ...metadata, image } as Metadata,
|
||||
};
|
||||
}
|
||||
|
||||
async function getImage(imgPath: string) {
|
||||
try {
|
||||
await access(path.join("public", imgPath));
|
||||
return imgPath;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMemberImagePath(name: string) {
|
||||
const imgPath = path.join("images", "team", name.replace(" ", ""));
|
||||
const placeholder = path.join(
|
||||
"images",
|
||||
"team",
|
||||
"team-member-placeholder.svg"
|
||||
);
|
||||
const img =
|
||||
(await getImage(imgPath + ".jpg")) ??
|
||||
(await getImage(imgPath + ".png")) ??
|
||||
(await getImage(imgPath + ".gif")) ??
|
||||
placeholder;
|
||||
return img;
|
||||
}
|
|
@ -10,8 +10,8 @@
|
|||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "next export",
|
||||
"lint": "eslint \"{pages,components,lib}/**/*.{js,ts,tsx,jsx}\" --quiet",
|
||||
"lint:fix": "eslint \"{pages,components,lib}/**/*.{js,ts,tsx,jsx}\" --quiet --fix"
|
||||
"lint": "eslint \"{pages,components,lib,hooks}/**/*.{js,ts,tsx,jsx}\" --quiet",
|
||||
"lint:fix": "eslint \"{pages,components,lib,hooks}/**/*.{js,ts,tsx,jsx}\" --quiet --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/loader": "^1.6.22",
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Default is light theme */
|
||||
--primary-background: #ffffff;
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
border: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: calc(24rem / 16);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.codey {
|
||||
width: calc(100rem / 16);
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# Meet the Team page
|
|
@ -0,0 +1,105 @@
|
|||
.headerContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: calc(24rem / 16);
|
||||
border-bottom: calc(1rem / 16) solid var(--primary-heading);
|
||||
margin-bottom: calc(46rem / 16);
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.headerTextContainer {
|
||||
margin: auto 0 0 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
color: var(--primary-heading);
|
||||
font-size: calc(48rem / 16);
|
||||
margin: 0 calc(53rem / 16) 0 0;
|
||||
}
|
||||
|
||||
.subheading {
|
||||
color: var(--primary-heading);
|
||||
font-size: calc(36rem / 16);
|
||||
font-weight: 600;
|
||||
padding-bottom: calc(22rem / 16);
|
||||
border-bottom: calc(1rem / 16) solid var(--primary-heading);
|
||||
margin-bottom: calc(46rem / 16);
|
||||
margin-top: calc(86rem / 16);
|
||||
}
|
||||
|
||||
.codey {
|
||||
width: calc(360rem / 16);
|
||||
}
|
||||
|
||||
.execs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(26rem / 16);
|
||||
margin-bottom: calc(86rem / 16);
|
||||
}
|
||||
|
||||
.members {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(calc(100rem / 16), 1fr));
|
||||
row-gap: calc(43rem / 16);
|
||||
column-gap: calc(53rem / 16);
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.elections {
|
||||
margin: 6rem 0;
|
||||
}
|
||||
|
||||
.electionSubheading {
|
||||
color: var(--primary-accent);
|
||||
font-size: calc(36rem / 16);
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: calc(768rem / 16)) {
|
||||
.headerContainer {
|
||||
flex-direction: column-reverse;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav {
|
||||
margin-top: calc(24rem / 16);
|
||||
margin-bottom: calc(46rem / 16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.codey {
|
||||
width: calc(140rem / 16);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: calc(24rem / 16);
|
||||
margin: calc(10rem / 16) 0 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subheading {
|
||||
font-size: calc(24rem / 16);
|
||||
padding-bottom: calc(15rem / 16);
|
||||
}
|
||||
|
||||
.execs,
|
||||
.members {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(calc(130rem / 16), 1fr));
|
||||
justify-items: center;
|
||||
column-gap: 0;
|
||||
}
|
||||
|
||||
.electionSubheading {
|
||||
font-size: calc(24rem / 16);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import React from "react";
|
||||
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
import { DefaultLayout } from "@/components/DefaultLayout";
|
||||
import { Image } from "@/components/Image";
|
||||
import { Link } from "@/components/Link";
|
||||
import { TeamMember } from "@/components/TeamMember";
|
||||
import { TeamMemberCard } from "@/components/TeamMemberCard";
|
||||
import {
|
||||
getExec,
|
||||
getExecNames,
|
||||
Metadata,
|
||||
getMemberImagePath,
|
||||
} from "@/lib/team";
|
||||
|
||||
import programmeData from "../../content/meet-the-team/programme-committee.json";
|
||||
import systemsData from "../../content/meet-the-team/systems-committee.json";
|
||||
import websiteData from "../../content/meet-the-team/website-committee.json";
|
||||
|
||||
import styles from "./team.module.css";
|
||||
|
||||
interface SerializedExec {
|
||||
content: MDXRemoteSerializeResult;
|
||||
metadata: Metadata;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
execs: SerializedExec[];
|
||||
programme: Metadata[];
|
||||
website: Metadata[];
|
||||
systems: Metadata[];
|
||||
}
|
||||
|
||||
export default function Team({ execs, programme, website, systems }: Props) {
|
||||
return (
|
||||
<>
|
||||
<DefaultLayout>
|
||||
<div className={styles.headerContainer}>
|
||||
<div className={styles.headerTextContainer}>
|
||||
<h1 className={styles.header}>Meet the Team!</h1>
|
||||
<div className={styles.nav}>
|
||||
<Link href="#execs">The Executives</Link>
|
||||
<Link href="#programme">Programme Committee</Link>
|
||||
<Link href="#website">Website Committee</Link>
|
||||
<Link href="#system">Systems Committee</Link>
|
||||
</div>
|
||||
<h2
|
||||
className={styles.subheading}
|
||||
style={{ borderBottom: "none", margin: 0, padding: 0 }}
|
||||
>
|
||||
The Executives
|
||||
</h2>
|
||||
</div>
|
||||
<Image src="images/team/team-codey.svg" className={styles.codey} />
|
||||
</div>
|
||||
<div className={styles.execs} id="execs">
|
||||
{execs.map((exec) => {
|
||||
return (
|
||||
<div key={exec.metadata.name}>
|
||||
<TeamMemberCard {...exec.metadata}>
|
||||
<MDXRemote {...exec.content} />
|
||||
</TeamMemberCard>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div id="programme">
|
||||
<h2 className={styles.subheading}>Programme Committee</h2>
|
||||
<MembersList team={programme} />
|
||||
</div>
|
||||
<div id="website">
|
||||
<h2 className={styles.subheading}>Website Committee</h2>
|
||||
<MembersList team={website} />
|
||||
</div>
|
||||
<div id="system">
|
||||
<h2 className={styles.subheading}>Systems Committee</h2>
|
||||
<MembersList team={systems} />
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
<div className={styles.elections}>
|
||||
<Bubble>
|
||||
<h2 className={styles.electionSubheading}>Elections</h2>
|
||||
To find out when and where the next elections will be held, keep an
|
||||
eye on on the <Link href="/#news">News</Link>. <br />
|
||||
For details on the elections, read our
|
||||
<Link href="/about/constitution"> Constitution</Link>
|
||||
</Bubble>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Team.Layout = function TeamLayout(props: { children: React.ReactNode }) {
|
||||
return <div>{props.children}</div>;
|
||||
};
|
||||
|
||||
interface MembersProps {
|
||||
team: Metadata[];
|
||||
}
|
||||
|
||||
function MembersList(props: MembersProps) {
|
||||
return (
|
||||
<div className={styles.members}>
|
||||
{props.team.map((member) => (
|
||||
<TeamMember {...member} key={member.name} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function getTeamWithImages(team: Omit<Metadata, "image">[]) {
|
||||
return await Promise.all(
|
||||
team.map(async (member) => {
|
||||
const image = await getMemberImagePath(member.name);
|
||||
return {
|
||||
...member,
|
||||
image,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
const execNames = await getExecNames();
|
||||
const execs = (await Promise.all(
|
||||
execNames.map((name) => getExec(name))
|
||||
)) as SerializedExec[];
|
||||
const [programme, website, systems] = await Promise.all([
|
||||
getTeamWithImages(programmeData),
|
||||
getTeamWithImages(websiteData),
|
||||
getTeamWithImages(systemsData),
|
||||
]);
|
||||
|
||||
return {
|
||||
props: { execs, programme, website, systems },
|
||||
};
|
||||
};
|
|
@ -68,7 +68,7 @@ export default function Home() {
|
|||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className={styles.news}>
|
||||
<section className={styles.news} id="news">
|
||||
<h1 className={styles.cardsHeading}>News</h1>
|
||||
<p className={styles.cardsDescription}>
|
||||
Updates from our execs! <br />
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" fill="#5CAFF9" fill-opacity="0.2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 323 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 877 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 246 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 4.2 MiB |
After Width: | Height: | Size: 786 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 218 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 2.2 MiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 847 KiB |
After Width: | Height: | Size: 968 KiB |
After Width: | Height: | Size: 733 KiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 336 KiB |
After Width: | Height: | Size: 461 KiB |
After Width: | Height: | Size: 277 KiB |
After Width: | Height: | Size: 862 KiB |
After Width: | Height: | Size: 1012 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 526 KiB |
After Width: | Height: | Size: 454 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 897 KiB |
After Width: | Height: | Size: 785 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 341 KiB |
After Width: | Height: | Size: 223 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 320 KiB |
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="2.57333" height="30.8317" rx="1.28666" transform="matrix(0.795101 -0.606477 0.710338 0.703861 0 1.83057)" fill="#C4C4C4"/>
|
||||
<rect width="2.58854" height="30.6416" rx="1.29427" transform="matrix(-0.820223 -0.572044 0.67815 -0.734924 2.87207 24)" fill="#C4C4C4"/>
|
||||
</svg>
|
After Width: | Height: | Size: 377 B |
After Width: | Height: | Size: 44 KiB |