Team page redesign updated (#1786)

This commit is contained in:
Celeste Roselli 2026-01-27 11:41:17 -05:00 committed by GitHub
parent 7a31866e23
commit 596ef1dbc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 2562 additions and 1994 deletions

View file

@ -62,21 +62,21 @@ const Prizes = ({
position: 'relative', position: 'relative',
transform: `rotate(${pRotate}deg)`, transform: `rotate(${pRotate}deg)`,
transitionDuration: '0.5s', transitionDuration: '0.5s',
opacity: stock == 0 ? '0.5' : 1, opacity: stock === 0 ? '0.5' : 1,
'&:hover': { '&:hover': {
transform: stock == 0 ? null : 'scale(1.1)', transform: stock === 0 ? null : 'scale(1.1)',
cursor: stock == 0 ? 'default' : 'pointer' cursor: stock === 0 ? 'default' : 'pointer'
} }
}} }}
onClick={e => { onClick={e => {
const div = e.currentTarget const div = e.currentTarget
const quantity = div.querySelector('[id*="dropdown-"]') const quantity = div.querySelector('[id*="dropdown-"]')
const buy = div.querySelector('#buy') const buy = div.querySelector('#buy')
if (quantity === e.target || buy == e.target) { if (quantity === e.target || buy === e.target) {
console.log('TRUE') console.log('TRUE')
return return
} else { } else {
stock != 0 stock !== 0
? document.getElementById(`${parsedFullName}-info`).showModal() ? document.getElementById(`${parsedFullName}-info`).showModal()
: null : null
} }
@ -98,7 +98,7 @@ const Prizes = ({
alt={text} alt={text}
/> />
</Flex> </Flex>
{stock <= 100 && stock != null && ( {stock <= 100 && stock !== null && (
<Text <Text
sx={{ sx={{
background: '#CC6CE7', background: '#CC6CE7',
@ -113,7 +113,7 @@ const Prizes = ({
variant="headline" variant="headline"
className="gaegu" className="gaegu"
> >
{stock == 0 ? <>Sold out</> : <>Only {stock} left! </>} {stock === 0 ? <>Sold out</> : <>Only {stock} left! </>}
</Text> </Text>
)} )}
<Text <Text
@ -161,7 +161,7 @@ const Prizes = ({
( (
hoursBalance hoursBalance
? hoursBalance / cost >= 2 ? hoursBalance / cost >= 2
? stock != 0 ? stock !== 0
: null : null
: null : null
) ? ( ) ? (
@ -182,7 +182,7 @@ const Prizes = ({
( (
hoursBalance hoursBalance
? hoursBalance / cost >= 1 ? hoursBalance / cost >= 1
? stock != 0 ? stock !== 0
: null : null
: null : null
) ? ( ) ? (
@ -289,7 +289,7 @@ const Prizes = ({
variant="headline" variant="headline"
className="gaegu" className="gaegu"
> >
{cost} {cost == 1 ? 'ticket' : 'tickets'} {cost} {cost === 1 ? 'ticket' : 'tickets'}
</Text> </Text>
<Flex <Flex
sx={{ sx={{
@ -378,7 +378,7 @@ const Prizes = ({
( (
hoursBalance hoursBalance
? hoursBalance / cost >= 2 ? hoursBalance / cost >= 2
? stock != 0 ? stock !== 0
: null : null
: null : null
) ? ( ) ? (
@ -398,7 +398,7 @@ const Prizes = ({
( (
hoursBalance hoursBalance
? hoursBalance / cost >= 1 ? hoursBalance / cost >= 1
? stock != 0 ? stock !== 0
: null : null
: null : null
) ? ( ) ? (

View file

@ -77,7 +77,7 @@ export default function ShopComponent({
> >
{availableItems {availableItems
.sort((a, b) => a['Cost Hours'] - b['Cost Hours']) .sort((a, b) => a['Cost Hours'] - b['Cost Hours'])
.filter(item => (item['Stock'] > 0 || item['Stock'] == null)) .filter(item => (item['Stock'] > 0 || item['Stock'] === null))
.map((item) => ( .map((item) => (
<Prizes <Prizes
img={item['Image URL']} img={item['Image URL']}
@ -101,7 +101,7 @@ export default function ShopComponent({
))} ))}
{availableItems {availableItems
.sort((a, b) => a['Cost Hours'] - b['Cost Hours']) .sort((a, b) => a['Cost Hours'] - b['Cost Hours'])
.filter(item => (item['Stock'] == 0)) .filter(item => (item['Stock'] === 0))
.map((item) => ( .map((item) => (
<Prizes <Prizes
img={item['Image URL']} img={item['Image URL']}

View file

@ -7,7 +7,7 @@ const CreateCard = ({ createCardLink }) => {
<> <>
<a href={createCardLink} className={styles.linkWrapper} rel="noopener noreferrer"> <a href={createCardLink} className={styles.linkWrapper} rel="noopener noreferrer">
<div className={styles.card}> <div className={styles.card}>
<img src={img}/> <img src={img} alt="Create a card" />
Create a card Create a card
</div> </div>
</a> </a>

View file

@ -52,7 +52,7 @@ const NewProjectForm = ({ authToken }) => {
success: 'Pulling repo data' success: 'Pulling repo data'
}} }}
sx={{ sx={{
background: status == 'error' ? '#DE4E2B' : '#09AFB4', background: status === 'error' ? '#DE4E2B' : '#09AFB4',
borderRadius: '10px' borderRadius: '10px'
}} }}
/> />

View file

@ -66,6 +66,7 @@ const ProjectView = ({
useEffect(() => { useEffect(() => {
setDarkColor(darkenColor(color, 0.8)) setDarkColor(darkenColor(color, 0.8))
setInvertedColor(invertColor(textColor)) setInvertedColor(invertColor(textColor))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [color]) }, [color])
// function convertToRawUrl(githubUrl) { // function convertToRawUrl(githubUrl) {
@ -133,8 +134,8 @@ const ProjectView = ({
}} }}
> >
<h1 className="slackey">{title}</h1> <h1 className="slackey">{title}</h1>
<h2>{description != 'Description Not Found' ? description : <></>}</h2> <h2>{description !== 'Description Not Found' ? description : <></>}</h2>
<h3>{user != 'User Not Found' ? <>By {user}</> : <></>}</h3> <h3>{user !== 'User Not Found' ? <>By {user}</> : <></>}</h3>
<div <div
className={styles.buttonGroup} className={styles.buttonGroup}
@ -218,13 +219,13 @@ const ProjectView = ({
display: 'grid', display: 'grid',
flexWrap: 'wrap', flexWrap: 'wrap',
gridTemplateColumns: gridTemplateColumns:
screenshot != '' && video != '' screenshot !== '' && video !== ''
? ['1fr', '1fr 1fr', '1fr 1fr'] ? ['1fr', '1fr 1fr', '1fr 1fr']
: '1fr', : '1fr',
gap: '10px' gap: '10px'
}} }}
> >
{image != '' && ( {image !== '' && (
<div <div
sx={{ sx={{
display: 'flex', display: 'flex',
@ -254,7 +255,7 @@ const ProjectView = ({
<p <p
className={styles.description} className={styles.description}
sx={{ textAlign: screenshot.length != 1 ? 'center' : 'left' }} sx={{ textAlign: screenshot.length !== 1 ? 'center' : 'left' }}
> >
<ReadmeRenderer markdown={markdown} /> <ReadmeRenderer markdown={markdown} />
</p> </p>

View file

@ -1,9 +1,9 @@
import Icon from '@hackclub/icons' import Icon from '@hackclub/icons'
import { useState } from 'react' import { useState } from 'react'
import { Avatar, Box, Card, Flex, Text } from 'theme-ui' import { Box, Card, Flex, Text } from 'theme-ui'
export default function Bio({ popup = true, spanTwo = false, ...props }) { export default function Bio({ popup = true, spanTwo = false, ...props }) {
const { img, name, teamRole, pronouns, text, subrole, email, href, video } = const { name, teamRole, pronouns, text, subrole, email, href, video, img } =
props props
const [expand, setExpand] = useState(false) const [expand, setExpand] = useState(false)
return ( return (
@ -36,23 +36,21 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) {
} }
}} }}
> >
<Avatar {img && (
size={64} <Box
width={64} as="img"
height={64} src={img}
mr={3} alt={name}
src={img} sx={{
alt={name} width: 96,
sx={{ height: 96,
overflow: 'hidden', minWidth: 96,
objectFit: 'cover', borderRadius: '50%',
transition: 'transform 0.125s ease-in-out', objectFit: 'cover',
'&:hover': { transform: 'rotate(-8deg) scale(1.25)' }, mr: 3
flexShrink: 0, }}
width: '64px', />
height: '64px' )}
}}
/>
<Box> <Box>
<Text sx={{ fontSize: [3, 3, 3] }} variant="headline" color="black"> <Text sx={{ fontSize: [3, 3, 3] }} variant="headline" color="black">
{name} {name}

239
components/boardbio.js Normal file
View file

@ -0,0 +1,239 @@
import Icon from '@hackclub/icons'
import { useState } from 'react'
import { Avatar, Box, Card, Flex, Text } from 'theme-ui'
export default function BoardBox({ popup = true, ...props }) {
const { img, name, teamRole, pronouns, text, subrole, email, href, video } = props
const [expand, setExpand] = useState(false)
return (
<>
<Card
bg="#d9f7ed"
sx={{
display: 'flex',
flexDirection: popup ? 'column' : 'row',
alignItems: popup ? 'center' : 'flex-start',
justifyContent: popup ? 'center' : 'flex-start',
transition: 'transform 0.125s ease-in-out',
'&:hover': { transform: 'scale(1.025)' },
cursor: (text && popup) || href ? 'pointer' : null,
textDecoration: 'none',
maxWidth: popup ? 'auto' : '600px',
zIndex: !popup ? 1003 : 5,
maxHeight: popup ? 'auto' : '90vh',
overflowY: 'hidden',
position: 'relative'
}}
as={href && !text ? 'a' : 'div'}
href={href}
target="_blank"
onClick={() => {
if (text && popup) {
setExpand(true)
}
}}
>
{popup ? (
<>
<Text
variant="headline"
sx={{ fontSize: subrole ? 3 : 4, textAlign: 'center', mb: subrole ? 0 : 1, mt: subrole ? -3: -2 }}
color="black"
>
{name}
</Text>
<Text
color="#24B5A5"
variant="subheadline"
sx={{ fontSize: subrole ? 1 : 3, textAlign: 'center', mb: subrole ? 0 : 2 }}
>
{teamRole}
</Text>
{subrole && (
<Text
color="#24B5A5"
sx={{
fontSize: 1,
textAlign: 'center',
mb: 2,
fontWeight: 400
}}
>
{subrole}
</Text>
)}
<Box
as="img"
src={img}
alt={name}
sx={{
width: '120px',
height: '120px',
borderRadius: '50%',
objectFit: 'cover',
mb: subrole ? -3: -2
}}
/>
</>
) : (
<>
<Avatar
size={64}
width={64}
height={64}
mr={3}
src={img}
alt={name}
sx={{
overflow: 'hidden',
objectFit: 'cover',
transition: 'transform 0.125s ease-in-out',
'&:hover': { transform: 'rotate(-8deg) scale(1.25)' },
flexShrink: 0,
width: '64px',
height: '64px'
}}
/>
<Box>
<Text sx={{ fontSize: [3, 3, 3] }} variant="headline" color="black">
{name}
</Text>
<Flex>
<Text>
<Text
color="#24B5A5"
variant="subheadline"
fontSize={2}
sx={{
mb: ['0px', '0px', '0px'],
fontSize: '1.1em',
width: 'fit-content'
}}
>
{teamRole}
</Text>
{subrole && (
<>
<br />
<Text
color="#24B5A5"
sx={{
mb: ['0px', '0px', '0px'],
fontSize: 1,
fontWeight: 400,
width: 'fit-content'
}}
>
{subrole}
</Text>
</>
)}
{pronouns && (
<Text fontSize={1} ml={1} color="muted" align="center">
({pronouns})
</Text>
)}
</Text>
</Flex>
{email &&
(email.includes('@') ? (
<Text color="muted" as={'a'} href={`mailto:${email}`}>
{email}
<br />
</Text>
) : (
<Text
color="muted"
as={'a'}
href={`mailto:${email}@hackclub.com`}
>
{email}@hackclub.com
<br />
</Text>
))}
<Text mt={2} mb={0} color="black">
{text}
</Text>
{video && (
<Flex
sx={{
marginTop: 4,
marginX: 5,
justifyContent: 'center',
aspectRatio: 4 / 3
}}
>
<iframe
width="100%"
src={video}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
/>
</Flex>
)}
{href && (
<Flex sx={{ alignItems: 'center' }}>
<Text
sx={{
transform: 'translateX(-4px) translateY(2px)',
display: 'inline-flex',
alignItems: 'center'
}}
>
<Icon glyph="external-fill" size={24} />
</Text>
<Text
mt={1}
mb={0}
color="black"
as={'a'}
target="_blank"
href={href}
sx={{ transform: 'translateX(-2px)' }}
>
{href}
</Text>
</Flex>
)}
</Box>
</>
)}
</Card>
{popup && expand && (
<Flex
sx={{
position: 'fixed',
zIndex: 1004,
top: 0,
left: 0,
height: '100vh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(0,0,0,0.6)',
pb: 4
}}
>
<BoardBox {...props} popup={false} />
<Flex
sx={{
position: 'fixed',
zIndex: 1002,
top: 0,
left: 0,
height: '100vh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
pb: 4
}}
onClick={() => setExpand(false)}
/>
</Flex>
)}
</>
)
}

View file

@ -5,7 +5,7 @@ const ForceTheme = ({ theme }) => {
const [colorMode, setColorMode] = useColorMode() const [colorMode, setColorMode] = useColorMode()
useEffect(() => { useEffect(() => {
setColorMode(theme) setColorMode(theme)
}, []) }, [setColorMode, theme])
return null return null
} }

View file

@ -18,7 +18,7 @@ export default function Blueprint({ stars, blueprintData }) {
setProjects('100+ projects built') setProjects('100+ projects built')
}) })
} }
}, []) }, [blueprintData])
return ( return (
<Box <Box

View file

@ -57,6 +57,7 @@ const ReplitForm = ({ cssDark }) => {
draggedSticker.current = null draggedSticker.current = null
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const handleInputChange = e => { const handleInputChange = e => {

View file

@ -51,6 +51,7 @@ export default function Join() {
</Box> </Box>
<Image <Image
src="https://cloud-j0p07nviw-hack-club-bot.vercel.app/0image.png" src="https://cloud-j0p07nviw-hack-club-bot.vercel.app/0image.png"
alt="Hack Club Slack community"
sx={{ sx={{
width: ['100%', '100%', '50%'], width: ['100%', '100%', '50%'],
height: ['100%', '100%', '30rem'], height: ['100%', '100%', '30rem'],

View file

@ -57,6 +57,7 @@ export default function Project({ title, description, color, img, itemId }) {
</Box> </Box>
<Image <Image
src={`/slack/${img}.png`} src={`/slack/${img}.png`}
alt={title}
sx={{ sx={{
visibility: ['visible'], visibility: ['visible'],
height: ['100%'], height: ['100%'],

View file

@ -14,7 +14,7 @@ export default function Quote({ text, person, age, location, img }) {
"{text}" "{text}"
</Text> </Text>
<Flex sx={{ gap: '8px' }}> <Flex sx={{ gap: '8px' }}>
<Image src={img} sx={{ height: 24, width: 24, borderRadius: 100 }} /> <Image src={img} alt={`${person}'s profile picture`} sx={{ height: 24, width: 24, borderRadius: 100 }} />
<Text as="h3" sx={{ fontWeight: 400 }}> <Text as="h3" sx={{ fontWeight: 400 }}>
{person}, {age} from {location} {person}, {age} from {location}
</Text> </Text>

110
pages/acknowledged.tsx Normal file
View file

@ -0,0 +1,110 @@
import { Box, Container, Flex, Grid, Text } from 'theme-ui'
import Meta from '@hackclub/meta'
import Head from 'next/head'
import Nav from '../components/nav'
import Footer from '../components/footer'
import Bio from '../components/bio'
import BoardBox from '../components/boardbio'
import ForceTheme from '../components/force-theme'
import { fetchTeam } from './api/team'
export default function Acknowleged({ team }) {
// Spacing between major team section boxes
const BOX_SPACING = 5
return (
<>
<Box as="main" key="main" pb={5}>
<ForceTheme theme="light" />
{/* @ts-expect-error -- TODO: fix this */}
<Nav />
<Meta
as={Head}
title="Team"
description="Meet the team that runs Hack Club, a global nonprofit network of high school computer science clubs."
/>
<Box
pt={6}
pb={5}
px={[2, 4]}
sx={{
backgroundImage:
'radial-gradient(ellipse farthest-corner at top left,rgb(36 181 165 / 70%),rgb(30 151 137 / 70%)), url(https://hc-cdn.hel1.your-objectstorage.com/s/v3/cf3488823b5ae7c41ed968224485ea06423a6862_IMG_9920.jpg)',
backgroundSize: 'cover',
backgroundPosition: '25% 15%'
}}
>
<Container>
<Text variant="ultratitle" color="snow">
By the students,
<br /> for the students.
</Text>
<Text
as="div"
variant="lead"
color="smoke"
sx={{ maxWidth: '650px' }}
>
We believe in a world where every young person is empowered to be
the change they want to see around them. At Hack Club, were
working hard to make it reality.
</Text>
</Container>
</Box>
<Container>
<Box sx={{ textAlign: 'center', mt: 100, mb: [3, 4] }}>
<Text
variant="title"
color="orange"
sx={{ lineHeight: '1em', fontSize: [4, 5, 6] }}
as="h2"
>
Acknowledgements
</Text>
<Text
variant="title"
color="text"
sx={{
lineHeight: '1.2',
fontSize: [1, 3, 4],
my: [3, 0, 0],
fontWeight: 400,
maxWidth: '600px',
width: '100%',
margin: 'auto'
}}
as="h2"
>
Thank you to everyone who helped shape Hack Club into what it is
today...
</Text>
</Box>
<Grid columns={[1, null, 2, 3]} gap={3}>
{team.acknowledged?.map(member => (
<Bio
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
key={member.name}
href={member.website}
/>
))}
</Grid>
</Container>
</Box>
<Footer light key="footer" />
</>
)
}
export const getServerSideProps = async () => {
try {
const team = await fetchTeam()
return { props: { team } }
} catch (e) {
return { props: { team: {} } }
}
}

View file

@ -38,7 +38,7 @@ export default async function handler(req, res) {
formURL: record.fields['Order Form URL'], formURL: record.fields['Order Form URL'],
description: record.fields['Description'], description: record.fields['Description'],
flavorText: record?.fields['Flavor text']?.map(recordID => { flavorText: record?.fields['Flavor text']?.map(recordID => {
const flavorRecord = data.flavor.find(f => f.id == recordID) const flavorRecord = data.flavor.find(f => f.id === recordID)
const result = { const result = {
message: flavorRecord.fields["Message"], message: flavorRecord.fields["Message"],
character: flavorRecord.fields["Character"], character: flavorRecord.fields["Character"],

View file

@ -21,7 +21,7 @@ export const shopParts = async () => {
if (stock && fields["Count of Orders Fulfilled"]) { if (stock && fields["Count of Orders Fulfilled"]) {
stock -= fields["Count of Orders Fulfilled"] stock -= fields["Count of Orders Fulfilled"]
} }
return { id: record.id, ...record.fields, "Stock": (stock == null)? null : (stock >= 0 ? stock : 0) } return { id: record.id, ...record.fields, "Stock": (stock === null)? null : (stock >= 0 ? stock : 0) }
}) })

View file

@ -23,7 +23,7 @@ export default async function handler(req, res) {
await rsvpsTable.create(fields) await rsvpsTable.create(fields)
res.status(200).json({ success: true }) res.status(200).json({ success: true })
} else if (req.method == 'GET') { } else if (req.method === 'GET') {
const result = await rsvpsTable.read() const result = await rsvpsTable.read()
res.status(200).json(result.length) res.status(200).json(result.length)

View file

@ -14,6 +14,8 @@ interface TeamMember {
website: string website: string
pronouns: string pronouns: string
avatar: string avatar: string
staff?: boolean
gapyear?: boolean
} }
export async function fetchTeam() { export async function fetchTeam() {

View file

@ -49,12 +49,13 @@ export default function Shop({
) )
useEffect(() => { useEffect(() => {
if (cat == 'all') { if (cat === 'all') {
setItems(availableItems) setItems(availableItems)
} else { } else {
let i = availableItems.filter(items => items['Category'].includes(cat)) let i = availableItems.filter(items => items['Category'].includes(cat))
setItems(i) setItems(i)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cat]) }, [cat])
const spotlightRef = useRef() const spotlightRef = useRef()
@ -232,7 +233,7 @@ export default function Shop({
🦢 Swag 🦢 Swag
</Button> </Button>
</Flex> </Flex>
{cat == 'all' ? ( {cat === 'all' ? (
<> <>
<Text <Text
sx={{ sx={{
@ -314,6 +315,7 @@ export default function Shop({
</Box> </Box>
<img <img
src="/arcade/o6.png" src="/arcade/o6.png"
alt="Decorative illustration"
sx={{ sx={{
width: ['30%', '30%', '30%', '40%'], width: ['30%', '30%', '30%', '40%'],
maxWidth: '210px', maxWidth: '210px',

View file

@ -257,10 +257,10 @@ const Powerups = ({
variant="headline" variant="headline"
className="gaegu" className="gaegu"
> >
{cost} {cost == 1 ? 'ticket' : 'tickets'} {cost} {cost === 1 ? 'ticket' : 'tickets'}
</Text> </Text>
{extraTags?.map((tag, i) => { {extraTags?.map((tag, i) => {
if (tag == 'Limited Supply') { if (tag === 'Limited Supply') {
return ( return (
<Text <Text
key={tag} key={tag}
@ -383,7 +383,7 @@ const Powerups = ({
variant="headline" variant="headline"
className="gaegu" className="gaegu"
> >
{cost} {cost == 1 ? 'ticket' : 'tickets'} {cost} {cost === 1 ? 'ticket' : 'tickets'}
</Text> </Text>
</dialog> </dialog>
</Flex> </Flex>
@ -407,7 +407,7 @@ const Intro = ({ title, num, text, img, third, ...props }) => {
className="gaegu" className="gaegu"
sx={{ sx={{
display: 'block', display: 'block',
width: third == 'true' ? ['100%', '100%', '100%', '70%'] : '100%' width: third === 'true' ? ['100%', '100%', '100%', '70%'] : '100%'
}} }}
> >
{title} {title}
@ -953,7 +953,7 @@ const Arcade = ({ stickers = [], carousel = [], highlightedItems = [] }) => {
paddingBottom: '20vh' paddingBottom: '20vh'
}} }}
> >
{slack == 'slack' ? ( {slack === 'slack' ? (
<Announcement <Announcement
copy="You were redirected as we're running a special summer event!" copy="You were redirected as we're running a special summer event!"
caption="To join our Slack, join ARCADE." caption="To join our Slack, join ARCADE."
@ -1105,6 +1105,7 @@ const Arcade = ({ stickers = [], carousel = [], highlightedItems = [] }) => {
> >
<img <img
src="/arcade/prizes.png" src="/arcade/prizes.png"
alt="Arcade prizes"
sx={{ sx={{
zIndex: 10, zIndex: 10,
width: ['80%', '70%', '65%', '80%'], width: ['80%', '70%', '65%', '80%'],
@ -1188,6 +1189,7 @@ const Arcade = ({ stickers = [], carousel = [], highlightedItems = [] }) => {
/> />
<img <img
src="/arcade/a1.png" src="/arcade/a1.png"
alt=""
sx={{ sx={{
width: '100px', width: '100px',
position: 'absolute', position: 'absolute',
@ -1571,6 +1573,7 @@ const Arcade = ({ stickers = [], carousel = [], highlightedItems = [] }) => {
/> />
<img <img
src="/arcade/r5.png" src="/arcade/r5.png"
alt=""
sx={{ sx={{
width: ['35%', '35%', '35%', '50%'], width: ['35%', '35%', '35%', '50%'],
maxWidth: '210px', maxWidth: '210px',
@ -1605,6 +1608,7 @@ const Arcade = ({ stickers = [], carousel = [], highlightedItems = [] }) => {
> >
<img <img
src="/arcade/o5.png" src="/arcade/o5.png"
alt=""
sx={{ sx={{
width: ['45%', '45%', '45%', '60%'], width: ['45%', '45%', '45%', '60%'],
maxWidth: '310px', maxWidth: '310px',
@ -1617,6 +1621,7 @@ const Arcade = ({ stickers = [], carousel = [], highlightedItems = [] }) => {
/> />
<img <img
src="/arcade/o6.png" src="/arcade/o6.png"
alt=""
sx={{ sx={{
width: ['30%', '30%', '30%', '40%'], width: ['30%', '30%', '30%', '40%'],
maxWidth: '210px', maxWidth: '210px',

View file

@ -51,13 +51,13 @@ export default function Shop({
) )
useEffect(() => { useEffect(() => {
if (cat == 'all') { if (cat === 'all') {
setItems(availableItems) setItems(availableItems)
} else { } else {
let i = availableItems.filter(items => items['Category'].includes(cat)) let i = availableItems.filter(items => items['Category'].includes(cat))
setItems(i) setItems(i)
} }
}, [cat]) }, [cat, availableItems])
// Spotlight effect // Spotlight effect
const spotlightRef = useRef() const spotlightRef = useRef()
@ -132,6 +132,7 @@ export default function Shop({
<Flag sx={{ display: 'block', zIndex: 4, ml: 5 }} /> <Flag sx={{ display: 'block', zIndex: 4, ml: 5 }} />
<img <img
src="/arcade/o6.png" src="/arcade/o6.png"
alt="Decorative dino"
sx={{ sx={{
width: ['30%', '30%', '30%', '40%'], width: ['30%', '30%', '30%', '40%'],
maxWidth: '210px', maxWidth: '210px',
@ -270,7 +271,7 @@ export default function Shop({
🦢 Swag 🦢 Swag
</Button> </Button>
</Flex> </Flex>
{cat == 'all' ? ( {cat === 'all' ? (
<> <>
<Text <Text
sx={{ sx={{

View file

@ -27,26 +27,28 @@ const flavorText = [
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const LoginPage = ({token}) => { const LoginPage = ({token}) => {
const [ status, setStatus ] = useState('Loading...') const [ status, setStatus ] = useState('Loading...')
useEffect(async () => { useEffect(() => {
const minWaitTime = sleep(3 * 1000) const run = async () => {
let data = {} const minWaitTime = sleep(3 * 1000)
const getTokenPromise = new Promise(async resolve => { let data = {}
const response = await fetch(`/api/arcade/showcase/login/${token}`, {method: 'POST'}) const getTokenPromise = new Promise(async resolve => {
data = await response.json() const response = await fetch(`/api/arcade/showcase/login/${token}`, {method: 'POST'})
resolve() data = await response.json()
}) resolve()
const [ _wait, _data ] = await Promise.all([minWaitTime, getTokenPromise]) })
const [ _wait, _data ] = await Promise.all([minWaitTime, getTokenPromise])
if (data.error) { if (data.error) {
setStatus(data.error) setStatus(data.error)
} else { } else {
setStatus("Redirecting!") setStatus("Redirecting!")
window.localStorage.setItem('arcade.authToken', data.authToken) window.localStorage.setItem('arcade.authToken', data.authToken)
await sleep(250) await sleep(250)
window.location.href = '/arcade/showcase/my' window.location.href = '/arcade/showcase/my'
}
} }
run()
}, []) }, [token])
return ( return (
<div> <div>

View file

@ -192,6 +192,7 @@ const My = () => {
if (now > deadline) { if (now > deadline) {
setSubmissionClose(true) setSubmissionClose(true)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
// const launchDate = new Date(2025, 7, 25, 8, 0, 0, 0) // const launchDate = new Date(2025, 7, 25, 8, 0, 0, 0)
@ -252,9 +253,12 @@ const My = () => {
} }
} }
useEffect(async () => { useEffect(() => {
loadProjects() const fetchData = async () => {
loadCohort() loadProjects()
loadCohort()
}
fetchData()
}, []) }, [])
return ( return (
@ -289,6 +293,7 @@ const My = () => {
<div sx={{ zIndex: 5, position: 'relative' }}> <div sx={{ zIndex: 5, position: 'relative' }}>
<img <img
src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png" src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png"
alt="Arcade"
sx={{ sx={{
width: '30%', width: '30%',
maxWidth: '200px', maxWidth: '200px',
@ -309,7 +314,7 @@ const My = () => {
}} }}
> >
<Text className="gaegu" sx={{ color: '#FF5C00' }}> <Text className="gaegu" sx={{ color: '#FF5C00' }}>
{status == 'success' ? `Welcome, ${name}` : ''} {status === 'success' ? `Welcome, ${name}` : ''}
</Text> </Text>
<div> <div>
@ -342,11 +347,11 @@ const My = () => {
</Heading> </Heading>
</SlideDown> </SlideDown>
{status == 'loading' && <Loading />} {status === 'loading' && <Loading />}
{status == 'error' && <ErrorMessage />} {status === 'error' && <ErrorMessage />}
{status == 'success' && ( {status === 'success' && (
<ProjectGallery <ProjectGallery
projects={projects} projects={projects}
loadProjects={loadProjects} loadProjects={loadProjects}

View file

@ -78,7 +78,7 @@ const Showcase = ({ projectID }) => {
console.error(e) console.error(e)
setStatus('error') setStatus('error')
}) })
}, []) }, [projectID])
return ( return (
<> <>
@ -121,6 +121,7 @@ const Showcase = ({ projectID }) => {
> >
<img <img
src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png" src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png"
alt="Arcade logo"
sx={{ sx={{
width: '30%', width: '30%',
maxWidth: '200px', maxWidth: '200px',

View file

@ -39,33 +39,37 @@ const ProjectShowPage = ({ projectID }) => {
const [status, setStatus] = useState('loading') const [status, setStatus] = useState('loading')
const [errorMsg, setError] = useState(null) const [errorMsg, setError] = useState(null)
useEffect(async () => { useEffect(() => {
const token = window.localStorage.getItem('arcade.authToken') const fetchProject = async () => {
const response = await fetch(`/api/arcade/showcase/projects/${projectID}`, { const token = window.localStorage.getItem('arcade.authToken')
method: 'GET', const response = await fetch(`/api/arcade/showcase/projects/${projectID}`, {
headers: { method: 'GET',
Authorization: `Bearer ${token}` headers: {
Authorization: `Bearer ${token}`
}
}).catch(e => {
console.error(e)
setStatus('error')
setError(e)
})
const data = await response.json()
if (data.error) {
setStatus('error')
return
} else {
setProject(data.project)
setStatus('success')
} }
}).catch(e => {
console.error(e)
setStatus('error')
setError(e)
})
const data = await response.json()
if (data.error) {
setStatus('error')
return
} else {
setProject(data.project)
setStatus('success')
} }
}, []) fetchProject()
}, [projectID])
return ( return (
<div> <div>
<div sx={{ zIndex: 5, position: 'relative' }}> <div sx={{ zIndex: 5, position: 'relative' }}>
<img <img
src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png" src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png"
alt="Arcade logo"
sx={{ sx={{
width: '30%', width: '30%',
maxWidth: '200px', maxWidth: '200px',
@ -76,11 +80,11 @@ const ProjectShowPage = ({ projectID }) => {
}} }}
/> />
<div className={styles.min}> <div className={styles.min}>
{status == 'loading' && <Loading />} {status === 'loading' && <Loading />}
{status == 'error' && <ErrorMessage />} {status === 'error' && <ErrorMessage />}
{status == 'success' && ( {status === 'success' && (
<ProjectView <ProjectView
key={project.id} key={project.id}
id={project.id} id={project.id}

View file

@ -1,4 +1,4 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef, useCallback } from 'react'
import { Button, Text, Box, Close, Flex } from 'theme-ui' import { Button, Text, Box, Close, Flex } from 'theme-ui'
import ProjectView from '../../../components/arcade/showcase/project-view' import ProjectView from '../../../components/arcade/showcase/project-view'
import SmallView from '../../../components/arcade/showcase/small-view-card' import SmallView from '../../../components/arcade/showcase/small-view-card'
@ -283,12 +283,12 @@ const Vote = () => {
} }
} }
useEffect(async () => { useEffect(() => {
loadProjects() loadProjects()
}, []) }, [])
/* get individual project details */ /* get individual project details */
const getProjectDetails = async () => { const getProjectDetails = useCallback(async () => {
const token = window.localStorage.getItem('arcade.authToken') const token = window.localStorage.getItem('arcade.authToken')
setStatus('loading') setStatus('loading')
@ -318,11 +318,11 @@ const Vote = () => {
console.error(e) console.error(e)
setStatus('error') setStatus('error')
} }
} }, [openProjectId])
useEffect(() => { useEffect(() => {
getProjectDetails() getProjectDetails()
}, [openProjectId]) }, [getProjectDetails])
const dialogRef = useRef(null) const dialogRef = useRef(null)
@ -438,7 +438,7 @@ const Vote = () => {
isCursorInsideBoundingBox(mousePosition, box) isCursorInsideBoundingBox(mousePosition, box)
) { ) {
insideVotingBox = true insideVotingBox = true
if (activeDroppableId != box.id) { if (activeDroppableId !== box.id) {
setActiveDroppable(true) setActiveDroppable(true)
setActiveDroppableId(box.id) setActiveDroppableId(box.id)
} }
@ -461,7 +461,7 @@ const Vote = () => {
console.log(destination) console.log(destination)
if (destination.droppableId == 'projects') { if (destination.droppableId === 'projects') {
console.log('projects') console.log('projects')
return return
} }
@ -607,7 +607,7 @@ const Vote = () => {
/* VOTE SUBMISSION BELOW */ /* VOTE SUBMISSION BELOW */
useEffect(() => { useEffect(() => {
if (Object.keys(votes).length == 5) { if (Object.keys(votes).length === 5) {
setIsButtonActive(true) setIsButtonActive(true)
} else { } else {
setIsButtonActive(false) setIsButtonActive(false)
@ -724,7 +724,7 @@ const Vote = () => {
//skips first render //skips first render
submitVote(overall, technical, creative) submitVote(overall, technical, creative)
} }
}, [endPage]) }, [endPage, creative, overall, technical])
//MOBILE w/ ChatGPT help //MOBILE w/ ChatGPT help
const [selectedProjects, setSelectedProjects] = useState(Array(5).fill('')) const [selectedProjects, setSelectedProjects] = useState(Array(5).fill(''))
@ -763,8 +763,8 @@ const Vote = () => {
setSelectedProjects(Array(5).fill('')) setSelectedProjects(Array(5).fill(''))
} }
return startVote == true ? ( return startVote === true ? (
endPage == true ? ( endPage === true ? (
<div <div
sx={{ sx={{
display: 'flex', display: 'flex',
@ -781,6 +781,7 @@ const Vote = () => {
> >
<img <img
src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png" src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png"
alt="Arcade logo"
sx={{ sx={{
width: '30%', width: '30%',
maxWidth: '200px', maxWidth: '200px',
@ -794,9 +795,9 @@ const Vote = () => {
className="gaegu" className="gaegu"
sx={{ textAlign: 'center', maxWidth: '800px' }} sx={{ textAlign: 'center', maxWidth: '800px' }}
> >
{submitStatus == 'loading' {submitStatus === 'loading'
? 'Loading...' ? 'Loading...'
: submitStatus == 'success' : submitStatus === 'success'
? 'Thanks for voting!' ? 'Thanks for voting!'
: 'Ran into an error sending your votes'} : 'Ran into an error sending your votes'}
</Text> </Text>
@ -806,7 +807,7 @@ const Vote = () => {
className="gaegu" className="gaegu"
sx={{ textAlign: 'center', maxWidth: '800px', mt: 0 }} sx={{ textAlign: 'center', maxWidth: '800px', mt: 0 }}
> >
{submitStatus == 'loading' ? 'It takes a while' : ''} {submitStatus === 'loading' ? 'It takes a while' : ''}
</Text> </Text>
<Button <Button
as="a" as="a"
@ -1018,7 +1019,7 @@ const Vote = () => {
transition: 'transform 0.2s ease', transition: 'transform 0.2s ease',
transform: transform:
activeDroppableId === voteId activeDroppableId === voteId
? activeDroppable == true ? activeDroppable === true
? 'scale(1.05)' ? 'scale(1.05)'
: 'scale(1)' : 'scale(1)'
: 'scale(1)' : 'scale(1)'
@ -1153,11 +1154,11 @@ const Vote = () => {
}} }}
className="gaegu" className="gaegu"
> >
{status == 'loading' && <Loading />} {status === 'loading' && <Loading />}
{status == 'error' && <ErrorMessage />} {status === 'error' && <ErrorMessage />}
{status == 'success' && ( {status === 'success' && (
<ProjectView <ProjectView
preview="preview" preview="preview"
key={openProject.id} key={openProject.id}
@ -1198,7 +1199,7 @@ const Vote = () => {
</div> </div>
) )
) )
) : startViewProject == true ? ( ) : startViewProject === true ? (
<div> <div>
<div <div
className="gaegu" className="gaegu"
@ -1300,6 +1301,7 @@ const Vote = () => {
> >
<img <img
src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png" src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png"
alt="Arcade logo"
sx={{ sx={{
width: '30%', width: '30%',
maxWidth: '200px', maxWidth: '200px',
@ -1312,8 +1314,8 @@ const Vote = () => {
sx={{ width: '90vw', margin: 'auto', maxWidth: '800px' }} sx={{ width: '90vw', margin: 'auto', maxWidth: '800px' }}
className="gaegu" className="gaegu"
> >
{loadStatus == 'success' ? ( {loadStatus === 'success' ? (
voted == true ? ( voted === true ? (
<div <div
sx={{ sx={{
display: 'flex', display: 'flex',
@ -1397,7 +1399,7 @@ const Vote = () => {
{loadStatus} {loadStatus}
</Text> </Text>
<Text variant="caption" as="h4"> <Text variant="caption" as="h4">
{loadStatus == 'loading' {loadStatus === 'loading'
? "Please give it some time. If it's taken too long, ask for help in #arcade-help" ? "Please give it some time. If it's taken too long, ask for help in #arcade-help"
: 'Try logging in again with /showcase in Slack. If it persists, please ask for help in #arcade-help.'} : 'Try logging in again with /showcase in Slack. If it persists, please ask for help in #arcade-help.'}
</Text> </Text>

View file

@ -37,12 +37,15 @@ function Gallery({ posts = [], tags = [] }) {
const [filterPosts, setFilterPosts] = useState([]); const [filterPosts, setFilterPosts] = useState([]);
const [filterParts, setFilterParts] = useState([]); const [filterParts, setFilterParts] = useState([]);
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => { useEffect(() => {
setAllPosts(posts); setAllPosts(posts);
setFilterParts([]); setFilterParts([]);
}, []); }, []);
/* eslint-enable react-hooks/exhaustive-deps */
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => { useEffect(() => {
setFilterPosts( setFilterPosts(
allPosts.filter(post => allPosts.filter(post =>
@ -50,6 +53,7 @@ function Gallery({ posts = [], tags = [] }) {
) )
); );
}, [filterParts]); }, [filterParts]);
/* eslint-enable react-hooks/exhaustive-deps */
const addFilter = (partID) => { const addFilter = (partID) => {

View file

@ -28,11 +28,14 @@ import { TypeAnimation } from 'react-type-animation'
const RsvpCount = () => { const RsvpCount = () => {
const targetRSVPs = 500 const targetRSVPs = 500
const [rsvpCount, setRsvpCount] = useState(0) const [rsvpCount, setRsvpCount] = useState(0)
useEffect(async () => { useEffect(() => {
// const url = 'https://api2.hackclub.com/v0.1/The Bin/rsvp' <- switch to this once we have api2 back up and running const fetchRsvpCount = async () => {
const url = '/api/bin/rsvp' // const url = 'https://api2.hackclub.com/v0.1/The Bin/rsvp' <- switch to this once we have api2 back up and running
const results = await fetch(url).then(r => r.json()) const url = '/api/bin/rsvp'
setRsvpCount(results) const results = await fetch(url).then(r => r.json())
setRsvpCount(results)
}
fetchRsvpCount()
}, []) }, [])
if (rsvpCount < targetRSVPs) { if (rsvpCount < targetRSVPs) {
@ -91,10 +94,13 @@ const PartPicker = () => {
const OnboardCount = () => { const OnboardCount = () => {
const [onboardCount, setOnboardCount] = useState(200) const [onboardCount, setOnboardCount] = useState(200)
useEffect(async () => { useEffect(() => {
const url = '/api/onboard/p/count' const fetchOnboardCount = async () => {
const results = await fetch(url).then(r => r.json()) const url = '/api/onboard/p/count'
setOnboardCount(results.count) const results = await fetch(url).then(r => r.json())
setOnboardCount(results.count)
}
fetchOnboardCount()
}, []) }, [])
return <Text>{onboardCount}</Text> return <Text>{onboardCount}</Text>
@ -106,7 +112,7 @@ const Electronic = ({ imageUrl, name, description }) => {
<Flex <Flex
sx={{ mx: 'auto', flexDirection: 'column', display: 'inline-flex' }} sx={{ mx: 'auto', flexDirection: 'column', display: 'inline-flex' }}
> >
<Image src={imageUrl} width="100" /> <Image src={imageUrl} width="100" alt={name} />
<Heading as="span" variant="headline"> <Heading as="span" variant="headline">
{name} {name}
</Heading> </Heading>
@ -207,6 +213,7 @@ export default function Bin() {
}}> }}>
<Image <Image
src="https://cloud-mt5wqf6f5-hack-club-bot.vercel.app/0rummaging.png" src="https://cloud-mt5wqf6f5-hack-club-bot.vercel.app/0rummaging.png"
alt="Rummaging through The Bin"
onClick={(e) => { fireConfetti(); crunch(); spinIt(e.target) }} onClick={(e) => { fireConfetti(); crunch(); spinIt(e.target) }}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
@ -223,7 +230,7 @@ export default function Bin() {
<RsvpCount /> <RsvpCount />
<Box id="rsvp"> <Box id="rsvp">
<Sparkles size="100px"> <Sparkles size="100px">
<Image src="https://cloud-rdlz8he4l-hack-club-bot.vercel.app/0thebin.svg" sx={{ maxWidth: '250px' }} /> <Image src="https://cloud-rdlz8he4l-hack-club-bot.vercel.app/0thebin.svg" alt="The Bin logo" sx={{ maxWidth: '250px' }} />
</Sparkles> </Sparkles>
</Box> </Box>
<Text sx={{ fontWeight: 'bold' }}> <Text sx={{ fontWeight: 'bold' }}>
@ -309,7 +316,7 @@ export default function Bin() {
<Box sx={{ textAlign: 'left' }}> <Box sx={{ textAlign: 'left' }}>
<Flex sx={{ my: 4 }}> <Flex sx={{ my: 4 }}>
<Box> <Box>
<Image src="https://cloud-mt5wqf6f5-hack-club-bot.vercel.app/0rummaging.png" /> <Image src="https://cloud-mt5wqf6f5-hack-club-bot.vercel.app/0rummaging.png" alt="Rummage" />
</Box> </Box>
<Box> <Box>
<Heading as="p" variant="headline"> <Heading as="p" variant="headline">
@ -321,10 +328,10 @@ export default function Bin() {
</Text> </Text>
</Box> </Box>
</Flex> </Flex>
<Image src="https://cloud-2wkwrydc4-hack-club-bot.vercel.app/0parts.svg" sx={{ width: '100%' }} /> <Image src="https://cloud-2wkwrydc4-hack-club-bot.vercel.app/0parts.svg" alt="Example parts" sx={{ width: '100%' }} />
<Flex sx={{ my: 4 }}> <Flex sx={{ my: 4 }}>
<Box> <Box>
<Image src="https://cloud-h7vwjlwe3-hack-club-bot.vercel.app/0frame_1__50_.png" /> <Image src="https://cloud-h7vwjlwe3-hack-club-bot.vercel.app/0frame_1__50_.png" alt="Think" />
</Box> </Box>
<Box> <Box>
<Text as="p" variant="headline"> <Text as="p" variant="headline">
@ -347,7 +354,7 @@ export default function Bin() {
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Image src="https://cloud-mt5wqf6f5-hack-club-bot.vercel.app/1prototype.png" /> <Image src="https://cloud-mt5wqf6f5-hack-club-bot.vercel.app/1prototype.png" alt="Prototype" />
</Box> </Box>
</Flex> </Flex>
<Box sx={{ <Box sx={{
@ -388,6 +395,7 @@ export default function Bin() {
</Flex> </Flex>
<Image <Image
src="https://cloud-ge8yutn2q-hack-club-bot.vercel.app/0image.png" src="https://cloud-ge8yutn2q-hack-club-bot.vercel.app/0image.png"
alt="Build it"
width="100%" width="100%"
/> />
</Box> </Box>

View file

@ -99,7 +99,7 @@ export const categories = [
color: 'blue', color: 'blue',
description: description:
'Everywhere from San Jose to Boston to New York, HCB powers teams of all sizes.', 'Everywhere from San Jose to Boston to New York, HCB powers teams of all sizes.',
match: org => org.category == 'robotics_team', match: org => org.category === 'robotics_team',
icon: 'sam' icon: 'sam'
}, },
{ {
@ -107,7 +107,7 @@ export const categories = [
id: 'hackathons', id: 'hackathons',
color: 'purple', color: 'purple',
description: `Hackers are using HCB to run hackathons that'll blow your mind away.`, description: `Hackers are using HCB to run hackathons that'll blow your mind away.`,
match: org => org.category == 'hackathon', match: org => org.category === 'hackathon',
icon: 'event-code' icon: 'event-code'
} }
] ]
@ -204,10 +204,10 @@ const FilterPanel = ({ filter, mobile, clearOffset }) => {
color: 'inherit', color: 'inherit',
fontSize: 3, fontSize: 3,
color: color:
category == availableCategory.id || category === availableCategory.id ||
(availableCategory.index && (availableCategory.index &&
category == null && category === null &&
region == null) region === null)
? 'primary' ? 'primary'
: 'null', : 'null',
':hover': { ':hover': {
@ -311,7 +311,7 @@ const FilterPanel = ({ filter, mobile, clearOffset }) => {
color: 'inherit', color: 'inherit',
fontSize: 3, fontSize: 3,
color: color:
region == kebabCase(availableRegion.label) region === kebabCase(availableRegion.label)
? 'primary' ? 'primary'
: 'null', : 'null',
':hover': { ':hover': {
@ -599,7 +599,7 @@ export default function Directory({ rawOrganizations, pageRegion, category }) {
sx={{ sx={{
textAlign: 'center', textAlign: 'center',
mb: 4, mb: 4,
display: searchValue == '' ? 'flex' : 'none', display: searchValue === '' ? 'flex' : 'none',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
gap: '16px' gap: '16px'
@ -809,7 +809,7 @@ export async function fetchRawOrganizations() {
page++ page++
total = [...total, ...json] total = [...total, ...json]
} }
return [...total.filter((a) => a.logo != null), ...total.filter((a) => a.logo == null)] return [...total.filter((a) => a.logo !== null), ...total.filter((a) => a.logo === null)]
} }
export const getStaticProps = async () => { export const getStaticProps = async () => {

View file

@ -4,8 +4,10 @@ import Head from 'next/head'
import Nav from '../components/nav' import Nav from '../components/nav'
import Footer from '../components/footer' import Footer from '../components/footer'
import Bio from '../components/bio' import Bio from '../components/bio'
import BoardBox from '../components/boardbio'
import ForceTheme from '../components/force-theme' import ForceTheme from '../components/force-theme'
import { fetchTeam } from './api/team' import { fetchTeam } from './api/team'
import Link from 'next/link'
const CommunityTeamBox = ({ title, children }) => { const CommunityTeamBox = ({ title, children }) => {
return ( return (
@ -22,7 +24,7 @@ const CommunityTeamBox = ({ title, children }) => {
<Text <Text
variant="headline" variant="headline"
as="h4" as="h4"
sx={{ textAlign: 'center', fontSize: 3 }} sx={{ textAlign: 'center', fontSize: 4 }}
> >
{title} {title}
</Text> </Text>
@ -40,6 +42,9 @@ const CommunityTeamBox = ({ title, children }) => {
} }
export default function Team({ team }) { export default function Team({ team }) {
// Spacing between major team section boxes
const BOX_SPACING = 5
return ( return (
<> <>
<Box as="main" key="main"> <Box as="main" key="main">
@ -82,156 +87,64 @@ export default function Team({ team }) {
</Box> </Box>
<Box bg="#f9f9fa" py={4}> <Box bg="#f9f9fa" py={4}>
<Container> <Container>
<Flex <Box sx={{
sx={{ mb: BOX_SPACING
bg: 'rgb(51 142 218 / 40%)', }}>
p: 3, <Text
borderRadius: '20px',
mb: 3,
gap: 3,
flexWrap: ['wrap', null, null, 'nowrap']
}}
>
<Text
variant="headline" variant="headline"
mt={2}
mb={3}
as="h3" as="h3"
sx={{ sx={{ textAlign: 'center', fontSize: 5 }}
textAlign: 'center',
fontSize: 3,
writingMode: [null, null, null, 'vertical-rl'],
mr: [0, 0, 0, 1],
transform: [null, null, null, 'rotate(180deg)'],
width: ['100%', null, null, 'fit-content'],
my: ['0px!important', '0px!important', '0px!important', 3]
}}
> >
Board & Advisors Board & Advisors
</Text> </Text>
<Box sx={{ flexGrow: 1 }}> <Grid columns={[1, null, 2]} gap={5} mb={4}>
<Grid columns={[1, null, 2]} gap={2} mb={2}> <BoardBox
<Bio img="/team/zach.jpg"
img="/team/zach.jpg" name="Zach Latta"
name="Zach Latta" teamRole="Founder"
teamRole="Founder" text="Zach dropped out of high school after his freshman year to work in the technology industry and had over 5 million people using his software by the time he turned 17. He founded Hack Club to build the program he wish he had in high school and has been awarded the Thiel Fellowship and Forbes 30 Under 30 for his work."
text="Zach dropped out of high school after his freshman year to work in the technology industry and had over 5 million people using his software by the time he turned 17. He founded Hack Club to build the program he wish he had in high school and has been awarded the Thiel Fellowship and Forbes 30 Under 30 for his work." email="zach"
pronouns="he/him" />
email="zach" <BoardBox
/> img="/team/christina.jpg"
<Bio name="Christina Asquith"
img="/team/christina.jpg" teamRole="Co-Founder and COO"
name="Christina Asquith" text="With more than a decade of experience in starting and leading organizations, Christina has built global teams and raised millions of dollars. She has 20 years experience as a journalist, including reporting for The New York Times from Iraq. She has an MA in education, and taught as a public school teacher in 2000, which inspired her book “The Emergency Teacher.”"
teamRole="Co-founder and COO" email="christina"
text="With more than a decade of experience in starting and leading organizations, Christina has built global teams and raised millions of dollars. She has 20 years experience as a journalist, including reporting for The New York Times from Iraq. She has an MA in education, and taught as a public school teacher in 2000, which inspired her book “The Emergency Teacher.”" />
pronouns="she/her"
email="christina"
/>
</Grid>
<Grid columns={[1, null, 3]} gap={2}>
<Bio
img="https://cloud-80nhjzldl-hack-club-bot.vercel.app/0.jpeg"
name="Tom Preston-Werner"
teamRole={<>Board Member</>}
subrole="Co-Founder, GitHub"
pronouns="he/him"
href="https://github.com/mojombo"
/>
<Bio
img="https://philanthropy.hackclub.com/_next/image?url=/quinn.png&w=1200&q=75"
name="Quinn Slack"
teamRole={<>Board Member</>}
subrole="CEO, Sourcegraph"
pronouns="he/him"
href="https://github.com/sqs"
/>
<Bio
img="https://media.licdn.com/dms/image/C5603AQFum8zxW-IEEA/profile-displayphoto-shrink_800_800/0/1517058384850?e=2147483647&v=beta&t=-oM8no3Zc7xUzCDBsHxajD_joBkQi8Ge5iPaeF5p0gM"
name="John Abele"
teamRole={<>Board Advisor</>}
href="https://en.wikipedia.org/wiki/John_Abele"
subrole="Founder, Boston Scientific"
pronouns="he/him"
/>
</Grid>
</Box>
</Flex>
<Grid columns={[1, null, 2]} gap={3}>
<Box>
<Box
sx={{
bg: 'rgb(51 214 166 / 40%)',
p: 3,
borderRadius: '20px'
}}
>
<Text
variant="headline"
mt={2}
mb={3}
as="h3"
sx={{ textAlign: 'center', fontSize: 4 }}
>
Hacker Resources Team
</Text>
<Grid columns={[1, null, 2]} gap={2}>
{team.current
?.filter(member => member.department === 'HQ')
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</Box>
</Box>
<Box>
<Box
sx={{
bg: 'rgb(236 55 80 / 40%)',
p: 3,
borderRadius: '20px'
}}
>
<Text
variant="headline"
mt={2}
mb={3}
as="h3"
sx={{ textAlign: 'center', fontSize: 4 }}
>
HCB Team
</Text>
<Grid columns={[1, null, 2]} gap={2}>
{team.current
?.filter(member => member.department === 'HCB')
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</Box>
</Box>
</Grid> </Grid>
<Grid columns={[1, null, 3]} gap={4} mb={4}>
<BoardBox
img="https://i.ibb.co/gMVMqJzt/2026-01-27-0io-Kleki.jpg"
name="Tom Preston-Werner"
teamRole={<>Board Member</>}
subrole="Co-Founder, GitHub"
href="https://github.com/mojombo"
/>
<BoardBox
img="https://i.ibb.co/qMCYrJn8/sqs.jpg"
name="Quinn Slack"
teamRole={<>Board Member</>}
subrole="Co-Founder and CEO, AMP"
href="https://github.com/sqs"
/>
<BoardBox
img="https://i.ibb.co/0pGTSmks/2026-01-27-0il-Kleki.png"
name="John Abele"
teamRole={<>Board Advisor</>}
href="https://en.wikipedia.org/wiki/John_Abele"
subrole="Founder, Boston Scientific"
/>
</Grid>
</Box>
<Box <Box
sx={{ sx={{
bg: 'rgb(166 51 214 / 40%)', bg: '#afcfee',
p: 3, p: 3,
borderRadius: '20px', borderRadius: '20px',
mt: 3 mb: BOX_SPACING
}} }}
> >
<Text <Text
@ -239,13 +152,193 @@ export default function Team({ team }) {
mt={2} mt={2}
mb={3} mb={3}
as="h3" as="h3"
sx={{ textAlign: 'center', fontSize: 4 }} sx={{ textAlign: 'center', fontSize: 5 }}
>
Hacker Resources Team
</Text>
{team.current?.filter(member => member.department === 'HQ' && member.staff).length > 0 && (
<>
<Text
variant="headline"
mt={2}
mb={2}
as="h4"
sx={{ fontSize: 3 }}
>
Staff:
</Text>
<Grid columns={[1, null, 2, 3]} gap={3}>
{team.current
?.filter(member => member.department === 'HQ' && member.staff)
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</>
)}
{team.current?.filter(member => member.department === 'HQ' && member.gapyear).length > 0 && (
<>
<Text
variant="headline"
mt={3}
mb={2}
as="h4"
sx={{ fontSize: 3 }}
>
2025-2026 Gap Years:
</Text>
<Grid columns={[1, null, 2, 3]} gap={3}>
{team.current
?.filter(member => member.department === 'HQ' && member.gapyear)
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</>
)}
{team.current?.filter(member => member.department === 'HQ' && !member.gapyear && !member.staff).length > 0 && (
<>
<Text
variant="headline"
mt={3}
mb={2}
as="h4"
sx={{ fontSize: 3 }}
>
Teen Contractors:
</Text>
<Grid columns={[1, null, 2, 3]} gap={3}>
{team.current
?.filter(member => member.department === 'HQ' && !member.gapyear && !member.staff)
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</>
)}
</Box>
<Box
sx={{
bg: 'rgb(236 55 80 / 40%)',
p: 3,
borderRadius: '20px',
mb: BOX_SPACING
}}
>
<Text
variant="headline"
mt={2}
mb={3}
as="h3"
sx={{ textAlign: 'center', fontSize: 5 }}
>
HCB Team
</Text>
{team.current?.filter(member => member.department === 'HCB' && member.staff).length > 0 && (
<>
<Text
variant="headline"
mt={2}
mb={2}
as="h4"
sx={{ fontSize: 3 }}
>
Staff:
</Text>
<Grid columns={[1, null, 2, 3]} gap={3}>
{team.current
?.filter(member => member.department === 'HCB' && member.staff)
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</>
)}
{team.current?.filter(member => member.department === 'HCB' && !member.staff).length > 0 && (
<>
<Text
variant="headline"
mt={3}
mb={2}
as="h4"
sx={{ fontSize: 3 }}
>
Contributors:
</Text>
<Grid columns={[1, null, 2, 3]} gap={3}>
{team.current
?.filter(member => member.department === 'HCB' && !member.staff)
.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
email={member.email}
href={member.website}
key={member.name}
/>
))}
</Grid>
</>
)}
</Box>
<Box
sx={{
bg: 'rgb(166 51 214 / 40%)',
p: 3,
borderRadius: '20px'
}}
>
<Text
variant="headline"
mt={2}
mb={3}
as="h3"
sx={{ textAlign: 'center', fontSize: 5 }}
> >
Community Team Community Team
</Text> </Text>
<Grid columns={[1, null, 2]} gap={3}> <Grid columns={[1, null, 2]} gap={3}>
<CommunityTeamBox title="Moderation"> <CommunityTeamBox title="Moderation">
<Grid columns={[1, null, 2]} gap={2} m={10}> <Grid columns={[1, null, 2]} gap={3} m={10}>
{team.current {team.current
?.filter(member => member.department === 'Moderation') ?.filter(member => member.department === 'Moderation')
.map(member => ( .map(member => (
@ -263,7 +356,7 @@ export default function Team({ team }) {
</Grid> </Grid>
</CommunityTeamBox> </CommunityTeamBox>
<CommunityTeamBox title="Welcomers"> <CommunityTeamBox title="Welcomers">
<Grid columns={[1, null, 2]} gap={2} m={10}> <Grid columns={[1, null, 2]} gap={3} m={10}>
{team.current {team.current
?.filter(member => member.department === 'Welcoming') ?.filter(member => member.department === 'Welcoming')
.map(member => ( .map(member => (
@ -281,7 +374,7 @@ export default function Team({ team }) {
</Grid> </Grid>
</CommunityTeamBox> </CommunityTeamBox>
<CommunityTeamBox title="Virtual Events"> <CommunityTeamBox title="Virtual Events">
<Grid columns={[1, null, 2]} gap={2} m={10}> <Grid columns={[1, null, 2]} gap={3} m={10}>
{team.current {team.current
?.filter(member => member.department === 'Events') ?.filter(member => member.department === 'Events')
.map(member => ( .map(member => (
@ -299,7 +392,7 @@ export default function Team({ team }) {
</Grid> </Grid>
</CommunityTeamBox> </CommunityTeamBox>
<CommunityTeamBox title="Newspaper"> <CommunityTeamBox title="Newspaper">
<Grid columns={[1, null, 2]} gap={2} m={10}> <Grid columns={[1, null, 2]} gap={3} m={10}>
{team.current {team.current
?.filter(member => member.department === 'Newspaper') ?.filter(member => member.department === 'Newspaper')
.map(member => ( .map(member => (
@ -319,46 +412,23 @@ export default function Team({ team }) {
</Grid> </Grid>
</Box> </Box>
<br /> <br />
<Box sx={{ textAlign: 'center', mt: 100, mb: [3, 4] }}> <Box sx={{ fontWeight: 'bold', textAlign: 'center' }}>
<Text <Link href="/acknowledged/">
variant="title" <Box sx={{ cursor: 'pointer' }}>
color="orange" <Text
sx={{ lineHeight: '1em', fontSize: [4, 5, 6] }} variant="title"
as="h2" color="orange"
> sx={{ lineHeight: '1em', fontSize: [4, 5, 6], textAlign: 'center', textDecoration: 'underline' }}
Acknowledgements as="h2"
</Text> >
<Text Acknowledgements
variant="title" </Text>
color="text" <Text sx={{ color: 'muted', fontSize: 2, mt: 2 }}>
sx={{ Thank you to everyone who helped shape Hack Club into what it is today...
lineHeight: '1.2', </Text>
fontSize: [1, 3, 4], </Box>
my: [3, 0, 0], </Link>
fontWeight: 400,
maxWidth: '600px',
width: '100%',
margin: 'auto'
}}
as="h2"
>
Thank you to everyone who helped shape Hack Club into what it is
today...
</Text>
</Box> </Box>
<Grid columns={[1, null, 2, 4]} gap={2}>
{team.acknowledged?.map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
key={member.name}
href={member.website}
/>
))}
</Grid>
</Container> </Container>
</Box> </Box>
</Box> </Box>

File diff suppressed because it is too large Load diff