From b0b4b8b7262e343cec31d8dd579329481ff3d833 Mon Sep 17 00:00:00 2001 From: arhum khan Date: Sat, 9 Mar 2024 18:20:49 -0800 Subject: [PATCH] fixed carousel + added current contributors/alumni --- frontend/package.json | 8 +- frontend/src/app/About/About.module.scss | 51 +++++---- frontend/src/app/About/AboutCarousel.tsx | 63 +++++------ .../src/app/About/Contributors.module.scss | 101 ++++++++++++++++++ frontend/src/app/About/Contributors.tsx | 96 +++++++++++++++++ frontend/src/app/About/index.tsx | 64 ++++++++--- 6 files changed, 305 insertions(+), 78 deletions(-) create mode 100644 frontend/src/app/About/Contributors.module.scss create mode 100644 frontend/src/app/About/Contributors.tsx diff --git a/frontend/package.json b/frontend/package.json index f1f778e03..62b8ad6eb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,12 +20,16 @@ "airtable": "^0.12.2", "bt": "^0.0.1", "classnames": "^2.5.1", + "glider": "^0.1.0", "iconoir-react": "^7.4.0", "moment": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-glider": "^4.0.2", + "react-multi-carousel": "^2.8.4", "react-router": "^6.21.3", - "react-router-dom": "^6.21.3" + "react-router-dom": "^6.21.3", + "swiper": "^11.0.7" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -40,7 +44,7 @@ "eslint-plugin-react-refresh": "^0.4.5", "g-sheets-api": "^2.2.0", "prettier": "^3.2.4", - "sass": "^1.70.0", + "sass": "^1.71.1", "typescript": "^5.3.3", "vite": "^5.0.12" } diff --git a/frontend/src/app/About/About.module.scss b/frontend/src/app/About/About.module.scss index c9a2e6c13..31cd55e2f 100644 --- a/frontend/src/app/About/About.module.scss +++ b/frontend/src/app/About/About.module.scss @@ -12,10 +12,19 @@ $bt-light-grey: #A0A0A0; .aboutOurTeam { text-align: center; + margin-top: 48px; + margin-bottom: 48px; + + h1 { + margin-bottom: 8px; + font-weight: bold; + } + p { color: $bt-light-text; line-height: 1.75; width: 500px; + margin-bottom: 16px; @media (max-width: 768px) { width: 300px; @@ -50,6 +59,12 @@ $bt-light-grey: #A0A0A0; display: none; transition: filter 1s ease, transform 1s ease; + >img { + max-width: 100%; // Ensure image does not exceed the container width + height: auto; // Maintain aspect ratio + object-fit: cover; // Cover the container fully + } + &.aboutCarouselActive { display: block; transform: translateX(0) scale(0.9); @@ -58,10 +73,9 @@ $bt-light-grey: #A0A0A0; &.aboutCarouselActivePrev { @extend .aboutCarouselActive; + order: 0; } - order: 0; - &.aboutCarouselActiveFirst { @extend .aboutCarouselActive; order: 1; @@ -84,12 +98,12 @@ $bt-light-grey: #A0A0A0; order: 4; } - &.focus-in { + &.focusIn { transform: scale(1); filter: brightness(1); } - &.focus-out { + &.focusOut { transform: scale(0.9); filter: brightness(0.35); } @@ -102,24 +116,19 @@ $bt-light-grey: #A0A0A0; } @media (min-width: 1024px) { - .aboutCarouselItem { - height: 400px; - width: 711px; - } + height: 400px; + width: 711px; } - @media (min-width: 768px) and (max-width: 1023px) { - .aboutCarouselItem { - height: 300px; - width: 533px; - } + + @media (min-width: 768px) and (max-width: 1023) { + height: 300px; + width: 533px; } @media (max-width: 767px) { - .aboutCarouselItem { - height: 200px; - width: 356px; - } + height: 200px; + width: 356px; } } @@ -197,7 +206,7 @@ $bt-light-grey: #A0A0A0; font-weight: bold; font-size: 24px; - width: 100%; + @media (max-width: 768px) { // Mobile @@ -220,14 +229,10 @@ $bt-light-grey: #A0A0A0; } } - - - - - .value { height: 100%; padding: 0 20px; + width: 600px; .valueContent { height: 100%; diff --git a/frontend/src/app/About/AboutCarousel.tsx b/frontend/src/app/About/AboutCarousel.tsx index d5596ae6a..c5a88c2e8 100644 --- a/frontend/src/app/About/AboutCarousel.tsx +++ b/frontend/src/app/About/AboutCarousel.tsx @@ -1,12 +1,8 @@ import { useEffect, useRef, useState } from 'react'; import { NavArrowLeft, NavArrowRight } from 'iconoir-react'; -import styles from "./About.module.scss" - -//import doe from 'assets/img/about/group/doe.jpg'; import michaels from '/images/michaels.jpeg'; import retreat from '/images/retreat.png'; -//import grace_janet from '/images/grace_janet.jpg'; import will from '/images/will.jpeg'; import jemma from '/images/jemma.jpeg'; import christina_janet from '/images/christina_janet.jpeg'; @@ -14,13 +10,20 @@ import retreat_silly from '/images/retreat_silly.jpg'; import zoom from '/images/zoom.png'; -//import logo from "/favicon.png"; - - +const images = [ + { img: retreat_silly, alt: 'retreat silly' }, + { img: zoom, alt: 'zoom' }, + { img: retreat, alt: 'retreat' }, + { img: christina_janet, alt: 'christina_janet' }, + { img: michaels, alt: 'michaels' }, + { img: will, alt: 'will' }, + { img: jemma, alt: 'jemma' } +]; +import styles from './About.module.scss'; enum Sliding { Still = 0, @@ -28,18 +31,6 @@ enum Sliding { Left = 2 } -const images = [ - { img: retreat_silly, alt: 'retreat silly' }, - /* { img: zoom, alt: 'zoom' }, */ - /* { img: doe, alt: 'doe' }, */ - /* { img: grace_janet, alt: 'grace_janet' }, */ - /* { img: retreat, alt: 'retreat' }, - { img: christina_janet, alt: 'christina_janet' }, - { img: michaels, alt: 'michaels' }, - { img: will, alt: 'will' }, */ - /* { img: jemma, alt: 'jemma' } */ -]; - const wrap = (val: number) => (val + images.length) % images.length; @@ -78,44 +69,42 @@ const AboutCarousel = () => { }; const getCarouselItemClass = (idx: number) => { - let classes = styles.aboutCarouselItem; + let classes = `${styles.aboutCarouselItem} `; if (idx === wrap(shownImage - 2)) { - classes += styles.aboutCarouselActivePrev; + classes += `${styles.aboutCarouselActivePrev} `;; } else if (idx === wrap(shownImage - 1)) { - classes += styles.aboutCarouselActiveFirst; + classes += `${styles.aboutCarouselActiveFirst} `; if (sliding === Sliding.Right) { - classes += styles.focusIn; + classes += `${styles.focusIn} `; } } else if (idx === shownImage) { - classes += styles.aboutCarouselActiveSecond; + classes += `${styles.aboutCarouselActiveSecond} `; if (sliding !== Sliding.Still) { - classes += styles.focusOut; + classes += `${styles.focusOut} `; } } else if (idx === wrap(shownImage + 1)) { - classes += styles.aboutCarouselActiveThird; + classes += `${styles.aboutCarouselActiveThird} `; if (sliding === Sliding.Left) { - classes += styles.focusIn; + classes += `${styles.focusIn} `;; } } else if (idx === wrap(shownImage + 2)) { - classes += styles.aboutCarouselActiveNext; + classes += `${styles.aboutCarouselActiveNext} `;; } - return classes; + return classes.trim(); }; const getCarouselClass = () => { - let classes = styles.aboutCarousel; + let classes = `${styles.aboutCarousel} `;; if (sliding === Sliding.Left) { - classes += styles.aboutCarouselSlideLeft; + classes += `${styles.aboutCarouselSlideLeft} `;; } else if (sliding === Sliding.Right) { - classes += styles.aboutCarouselSlideRight; + classes += `${styles.aboutCarouselSlideRight} `;; } - console.log(classes) - return classes; - + return classes.trim(); }; return ( -
+
{ @@ -123,7 +112,7 @@ const AboutCarousel = () => { }} > {images.map((imgVal, index) => ( -
+
{imgVal.alt}
))} diff --git a/frontend/src/app/About/Contributors.module.scss b/frontend/src/app/About/Contributors.module.scss new file mode 100644 index 000000000..57428decd --- /dev/null +++ b/frontend/src/app/About/Contributors.module.scss @@ -0,0 +1,101 @@ +$z-index-serious: 1; +$bt-grey-text: #535353; +$bt-light-text: #8A8A8A; +$bt-button-background: #8e9fc81c; + +.currentContributors, +.pastContributors { + display: flex; + flex-direction: column; + margin-bottom: 48px; + + h1 { + margin-bottom: 24px; + font-weight: bold; + } + + >div { + display: grid; + grid-template-columns: repeat(4, 150px); + justify-content: center; + flex-wrap: wrap; + gap: 25px; + + @media (max-width: 768px) { + grid-template-columns: repeat(2, 150px); + } + } +} + +.pastContributors { + margin-bottom: 16px; +} + +.contributorCard { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-bottom: 25px; + + // use width/height to enforce aspect ratio i guess + .headshot { + + width: 150px; + height: 150px; + + margin-bottom: 10px; + + img { + width: 150px; + height: 150px; + + position: absolute; + object-fit: cover; + object-position: 50% 20%; + border-radius: 3px; + } + + .serious { + z-index: $z-index-serious; + transition: 0.2s; + + &:hover { + opacity: 0; + } + } + } + + .name { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + width: 100%; + margin-bottom: 3px; + + p { + font-weight: 500; + color: $bt-grey-text; + } + + svg { + width: 22px; + height: 21px; + padding: 2px; + transition: 0.2s; + border-radius: 4px; + margin-bottom: 2px; + + &:hover { + background-color: $bt-button-background; + } + } + } + + .role { + font-size: 14px; + word-wrap: normal; + color: $bt-light-text; + } +} \ No newline at end of file diff --git a/frontend/src/app/About/Contributors.tsx b/frontend/src/app/About/Contributors.tsx new file mode 100644 index 000000000..e88d37f60 --- /dev/null +++ b/frontend/src/app/About/Contributors.tsx @@ -0,0 +1,96 @@ +import { Globe } from 'iconoir-react'; +import { contributorStructure } from './index'; +import styles from "./Contributors.module.scss" + +interface ContributorsProps { + currContributors: contributorStructure[]; + alumniContributors: contributorStructure[]; +} + + +const Contributors: React.FC = ({ currContributors, alumniContributors }) => { + /* console.log(currContributors) + console.log(alumniContributors) + console.log(currContributors[0]?.name) + console.log(currContributors[0]?.img.seriousBase64); + + */ + + console.log(currContributors) + const base64Root = 'data:image/jpeg;base64,' + + const alumniByGradYear: { [key: number]: contributorStructure[] } = {}; + + // Populate the data structure + alumniContributors.forEach((member) => { + const gradYear = member.gradYr; + if (gradYear in alumniByGradYear) { + alumniByGradYear[gradYear].push(member); + } else { + alumniByGradYear[gradYear] = [member]; + } + }); + + + + return ( + <> +
+

+ Current Contributors +

+
+ {currContributors.map(({ name, img, websiteURL, role }) => ( +
+
+ {name} + {name} +
+
+

{name}

+ {websiteURL ? ( + + + + ) : null} +
+
{role}
+
+ ))} +
+
+ +
+

+ Alumni +

+ {Object.entries(alumniByGradYear).map(([key, alumniList]) => ( +
+

+ {key.toString() === '00' ? 'Founders' : (key.toString() === '01' ? 'Founding Team' : `Class of ${key.toString()}`)} +

+
+ {alumniList.map((alumni) => ( // Iterate over each alumni in the list +
+
+

{alumni.name}

+ {alumni.websiteURL && ( + + + + )} +
+ {alumni.role &&
+ {key === '00' ? 'Co-Founder' : alumni.role} +
} +
+ ))} +
+
+ ))} +
+ + ); +}; + +export default Contributors; \ No newline at end of file diff --git a/frontend/src/app/About/index.tsx b/frontend/src/app/About/index.tsx index a29a21e45..697c63ebc 100644 --- a/frontend/src/app/About/index.tsx +++ b/frontend/src/app/About/index.tsx @@ -1,13 +1,10 @@ -//import { Row, Col } from 'react-bootstrap'; import { Leaf, LightBulbOn, EmojiTalkingHappy } from 'iconoir-react'; import Airtable from 'airtable'; - import { useState, useEffect } from 'react'; +import Contributors from './Contributors'; -//import { H3, P } from 'bt/custom'; - //import CurrentContributors from '../components/About/CurrentContributors'; //import PastContributors from '../components/About/PastContributors'; @@ -15,9 +12,25 @@ import AboutCarousel from './AboutCarousel'; import styles from "./About.module.scss" +export interface contributorStructure { + name: string; + gradYr: number; + role: string; + img: { + seriousBase64: string, + sillyBase64: string | null, + }; + websiteURL: string; + isAlumni: boolean +}; + export default function About() { - const currentTeam: { name: string; date: string, pictureBase64: any }[] = []; + + + + const [currContributors, setCurrContributors] = useState([]); + const [alumniContributors, setAlumniContributors] = useState([]); useEffect(() => { async function fetchData() { @@ -82,19 +95,31 @@ export default function About() { }).eachPage(async function page(records: ReadonlyArray, fetchNextPage) { // Mark this function as async // Map over records to create an array of promises const promises = records.map(async record => { - const pictureUrl = record.get("Picture")[0].url; - const pictureBase64 = await convertURLToBase64(pictureUrl); // Await the conversion + const seriousPicUrl = record.get("SeriousPicture")?.[0]?.url ?? null; + const sillyPicUrl = record.get("SillyPicture")?.[0]?.url ?? null; + console.log(sillyPicUrl) + const seriousBase64 = await convertURLToBase64(seriousPicUrl) as string; + const sillyBase64 = sillyPicUrl ? await convertURLToBase64(sillyPicUrl) as string : null; + const gradYear = record.get("GradYear") // Await the conversion + return { name: record.get("Name"), - date: record.get("Date"), - pictureBase64 // This will be a base64 string + gradYr: gradYear, + role: record.get("Role"), + img: { + seriousBase64: seriousBase64, + sillyBase64: sillyBase64, + }, // This will be a base64 string + websiteURL: record.get("Website"), + isAlumni: gradYear < (new Date().getFullYear()), }; }); // Wait for all promises to resolve - const teamMembers = await Promise.all(promises); + const teamMember = await Promise.all(promises); // Push each team member into the currentTeam array - teamMembers.forEach(member => currentTeam.push(member)); + setCurrContributors(prevCurr => [...prevCurr, ...teamMember.filter(member => !member.isAlumni)]); + setAlumniContributors(prevAlumni => [...prevAlumni, ...teamMember.filter(member => member.isAlumni)]); fetchNextPage(); }, function done(err) { @@ -103,21 +128,25 @@ export default function About() { } - console.log(currentTeam) + + //console.log(allMembers) + // console.log(currContributors) + //console.log(alumniContributors) return (
-

+

About Our Team

-

+

We're a small group of student volunteers at UC Berkeley, dedicated to simplifying the course discovery experience. We actively build, improve and maintain Berkeleytime.

{/* */}
- {/* */} + +
Our Values
@@ -131,7 +160,6 @@ export default function About() {

-
@@ -155,6 +183,10 @@ export default function About() {
+ {/* */}