diff --git a/.gitignore b/.gitignore index c1564e4a..315031bf 100755 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ yarn-error.log bun.lockb .idea .yarn -.yarnrc.yml \ No newline at end of file +.yarnrc.yml +.env*.local diff --git a/components/bio.js b/components/bio.js index d79a92ff..4dacc300 100644 --- a/components/bio.js +++ b/components/bio.js @@ -3,7 +3,7 @@ import { useState } from 'react' import { Avatar, Box, Card, Flex, Text } from 'theme-ui' export default function Bio({ popup = true, spanTwo = false, ...props }) { - let { img, name, teamRole, pronouns, text, subrole, email, href, video } = + const { img, name, teamRole, pronouns, text, subrole, email, href, video } = props const [expand, setExpand] = useState(false) return ( @@ -16,8 +16,7 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) { display: 'flex', alignItems: popup ? 'center' : 'flex-start', transition: 'transform 0.125s ease-in-out', - '&:hover': - { transform: 'scale(1.025)' }, + '&:hover': { transform: 'scale(1.025)' }, cursor: (text && popup) || href ? 'pointer' : null, textDecoration: 'none', maxWidth: '600px', @@ -25,7 +24,7 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) { maxHeight: '90vh', overflowY: 'hidden', overscrollBehavior: 'contain', - gridColumn: !spanTwo ? null : [null, null, `1 / span 2`], + gridColumn: !spanTwo ? null : [null, null, '1 / span 2'], position: 'relative' }} as={href && !text ? 'a' : 'div'} @@ -95,12 +94,23 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) { )} - {!popup && email && ( - - {email}@hackclub.com -
-
- )} + {!popup && + email && + (email.includes('@') ? ( + + {email} +
+
+ ) : ( + + {email}@hackclub.com +
+
+ ))} {!popup && ( <> @@ -123,7 +133,7 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) { frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen - > + /> )} @@ -145,6 +155,7 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) { color="black" as={'a'} href={href} + target="_blank" sx={{ transform: 'translateX(-2px)' }} > {href} @@ -154,38 +165,36 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) { {popup && expand && ( - <> + + - - setExpand(false)} - > - - + onClick={() => setExpand(false)} + /> + )} ) diff --git a/components/collapsable-box.js b/components/collapsable-box.js new file mode 100644 index 00000000..9b09ed21 --- /dev/null +++ b/components/collapsable-box.js @@ -0,0 +1,64 @@ +import { useState, useRef, useEffect } from 'react' +import { Box, Text } from 'theme-ui' + +// Not used atm, but keeping around in case we want to add back in +const CollapsableBox = ({ + title, + children, + backgroundColor, + isOpen: isOpenProp +}) => { + const [isOpen, setIsOpen] = useState(isOpenProp || false) + const [height, setHeight] = useState(0) + const contentRef = useRef(null) + + useEffect(() => { + if (contentRef.current) { + setHeight(isOpen ? contentRef.current.scrollHeight : 0) + } + }, [isOpen]) + + const toggleOpen = () => { + setIsOpen(prev => !prev) + } + + return ( + +
event.key === 'Enter' && toggleOpen()} + style={{ cursor: 'pointer', fontWeight: 'bold' }} + > + + {title} + +
+
+ {children} +
+
+ ) +} + +export default CollapsableBox diff --git a/components/fiscal-sponsorship/first/stats.js b/components/fiscal-sponsorship/first/stats.js index bf2d0d41..8d9caab5 100644 --- a/components/fiscal-sponsorship/first/stats.js +++ b/components/fiscal-sponsorship/first/stats.js @@ -65,7 +65,10 @@ const Stats = ({ stats }) => { return () => observer.disconnect() }, [stats.transactions_volume]) - if (stats.transactions_volume === undefined) return null + if (stats.transactions_volume === undefined) { + return null + } + return ( diff --git a/components/footer.js b/components/footer.js index d64ded06..7d917828 100644 --- a/components/footer.js +++ b/components/footer.js @@ -52,7 +52,7 @@ const Service = ({ href, icon, name = '', ...props }) => ( const Footer = ({ dark = false, email = 'team@hackclub.com', - children, + children = undefined, ...props }) => ( +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.mjs b/next.config.mjs index fce8f3d3..d4f2aa32 100755 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,7 +5,7 @@ const nextConfig = { ignoreDuringBuilds: true }, trailingSlash: true, - pageExtensions: ['js', 'jsx', 'mdx'], + pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'], images: { domains: [ 'hackclub.com', @@ -28,7 +28,6 @@ const nextConfig = { return config }, async redirects() { - return [ { source: '/bank/:path*', @@ -201,6 +200,7 @@ const nextConfig = { source: '/github', destination: 'https://github.com/hackclub', permanent: true + }, { source: '/nest', diff --git a/package.json b/package.json index 144ff1d8..c9528de3 100644 --- a/package.json +++ b/package.json @@ -12,21 +12,21 @@ "format": "prettier --write ." }, "dependencies": { - "@apollo/client": "^3.9.11", - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@fillout/react": "^1.1.2", + "@apollo/client": "^3.13.1", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fillout/react": "^1.1.6", "@github/time-elements": "^4.0.0", "@hackclub/icons": "^0.0.14", "@hackclub/markdown": "^0.0.43", "@hackclub/meta": "1.1.32", "@hackclub/theme": "^0.3.3", "@mdx-js/loader": "^1.6.22", - "@next/mdx": "^14.1.0", - "@octokit/auth-app": "^6.0.1", - "@octokit/core": "^5.1.0", - "@octokit/rest": "^20.0.2", - "@sendgrid/mail": "^8.1.1", + "@next/mdx": "^14.2.24", + "@octokit/auth-app": "^6.1.3", + "@octokit/core": "^5.2.0", + "@octokit/rest": "^20.1.2", + "@sendgrid/mail": "^8.1.4", "@tracespace/core": "^5.0.0-alpha.0", "@tracespace/identify-layers": "^5.0.0-alpha.0", "@tracespace/parser": "^5.0.0-next.0", @@ -37,19 +37,19 @@ "airtable-plus": "^1.0.4", "animated-value": "^0.2.4", "animejs": "^3.2.2", - "axios": "^1.6.7", + "axios": "^1.8.1", "camelcase": "^8.0.0", - "cookies-next": "^4.0.0", + "cookies-next": "^4.3.0", "country-list": "^2.3.0", "country-list-js": "^3.1.8", - "cursor-effects": "^1.0.15", + "cursor-effects": "^1.0.17", "date-fns": "^2.30.0", - "devtools-detect": "^4.0.1", - "form-data": "^4.0.0", + "devtools-detect": "^4.0.2", + "form-data": "^4.0.2", "fuzzysort": "^2.0.4", "geopattern": "^1.2.3", - "globby": "^11.0.4", - "graphql": "^16.8.1", + "globby": "^11.1.0", + "graphql": "^16.10.0", "howler": "^2.2.4", "js-confetti": "^0.12.0", "jszip": "^3.10.1", @@ -57,56 +57,57 @@ "lodash": "^4.17.21", "luxon": "^3.5.0", "million": "^2.6.4", - "next": "^12.3.1", + "next": "^12.3.4", "next-transpile-modules": "^10.0.1", "nextjs-current-url": "^1.0.3", - "openai": "^4.42.0", + "openai": "^4.86.1", "pcb-stackup": "^4.2.8", - "rc-dialog": "^9.5.2", + "rc-dialog": "^9.6.0", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", "react-before-after-slider-component": "^1.1.8", "react-countdown": "^2.3.6", - "react-datepicker": "^4.24.0", + "react-datepicker": "^4.25.0", "react-dom": "^17.0.2", - "react-horizontal-scrolling-menu": "^6.0.2", + "react-horizontal-scrolling-menu": "^6.1.0", "react-konami-code": "^2.3.0", "react-lite-youtube-embed": "^2.4.0", - "react-markdown": "^8", + "react-markdown": "^8.0.7", "react-marquee-slider": "^1.1.5", "react-masonry-css": "^1.0.16", "react-page-visibility": "^7.0.0", "react-relative-time": "^0.0.9", "react-responsive-carousel": "^3.2.23", "react-reveal": "^1.2.2", - "react-router-dom": "^6.22.3", + "react-router-dom": "^6.30.0", "react-scrolllock": "^5.0.1", "react-snowfall": "^1.2.1", - "react-syntax-highlighter": "^15.5.0", + "react-syntax-highlighter": "^15.6.1", "react-ticker": "^1.3.2", "react-tooltip": "^4.5.1", "react-tsparticles": "^2.12.2", "react-type-animation": "^3.2.0", - "react-use-websocket": "^4.8.1", - "react-wrap-balancer": "^1.1.0", + "react-use-websocket": "^4.13.0", + "react-wrap-balancer": "^1.1.1", "recharts": "2.12.2", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark": "^15.0.1", "remark-gfm": "^3.0.1", "remark-html": "^16.0.1", - "styled-components": "^6.1.8", - "swr": "^2.2.4", - "theme-ui": "^0.14", + "styled-components": "^6.1.15", + "swr": "^2.3.2", + "theme-ui": "^0.14.7", "tinytime": "^0.2.6", - "turndown": "^7.1.2", + "turndown": "^7.2.0", "vanilla-tilt": "^1.8.1", - "yarn": "^1.22.21" + "yarn": "^1.22.22" }, "devDependencies": { "eslint": "8.57.0", "eslint-config-next": "14.1.0", - "prettier": "^3.2.5" + "prettier": "^3.5.3", + "typescript": "^5.8.2" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/pages/api/team.js b/pages/api/team.js deleted file mode 100644 index 7d972f4a..00000000 --- a/pages/api/team.js +++ /dev/null @@ -1,86 +0,0 @@ -import AirtablePlus from 'airtable-plus' - -const airtable = new AirtablePlus({ - baseID: 'app79wD9AFys1NMUp', - apiKey: process.env.AIRTABLE_API_KEY, - tableName: 'Current', -}) - -const cache = { - refreshed_at: 0, - current: [], - acknowledged: [] -}; - -export async function fetchTeam() { - if ((cache.refreshed_at + (5 * 60 * 1000)) >= Date.now()) { - return cache - } - - const records = await airtable.read(); - const current = []; - const acknowledged = []; - - for (let record of records.sort((a, b) => a.fields['Order'] - b.fields['Order'])) { - const member = { - name: record.fields["Name"], - bio: record.fields["Bio"] || null, - department: record.fields["Department"], - role: record.fields["Role"], - bio_hackfoundation: null, - pronouns: null, - slack_id: record.fields["Slack ID"] || null, - slack_display_name: "", - avatar: record.fields["Override Avatar"] ? record.fields["Override Avatar"][0].thumbnails.large.url : null, - avatar_id: "", - email: record.fields["Email"] || null, - website: record.fields["Website"] || null, - } - - if (process.env.SLACK_API_TOKEN) { - const slackData = await fetch( - 'https://hackclub.slack.com/api/users.profile.get?user=' + record.fields["Slack ID"], - { - method: 'POST', - headers: { - 'content-type': 'multipart/form-data; boundary=----orpheus', - cookie: process.env.SLACK_API_COOKIE - }, - body: `------orpheus\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\n${process.env.SLACK_API_TOKEN}\r\n------orpheus\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\n${record.fields["Slack ID"]}\r\n------orpheus\r\n` - } - ).then(r => r.json()); - - if (slackData.ok) { - if (!record.fields["Override Avatar"]) { - member.avatar = `https://ca.slack-edge.com/T0266FRGM-${record.fields["Slack ID"]}-${slackData.profile.avatar_hash}-128` - } - member.pronouns = slackData.profile.pronouns - } - } - - if (record.fields["Acknowledged"]) { - acknowledged.push(member) - } else { - current.push(member) - } - } - - cache.current = current; - cache.acknowledged = acknowledged; - - cache.refreshed_at = Date.now(); - - return { - current, - acknowledged - } -} - -export default async function handler(req, res) { - const team = await fetchTeam() - - res.status(200).json({ - refreshed_at: cache.refreshed_at, - ...team - }); -} diff --git a/pages/api/team.ts b/pages/api/team.ts new file mode 100644 index 00000000..b06adb82 --- /dev/null +++ b/pages/api/team.ts @@ -0,0 +1,63 @@ +import teamMembers from '../../public/team.json' +import type { NextApiRequest, NextApiResponse } from 'next' + +interface TeamMember { + name: string + department: string + role: string | string[] + acknowledged: boolean + bio: string + bioHackFoundation: string + slackId: string + overrideAvatar: string + email: string + website: string + pronouns: string + avatar: string +} + +export async function fetchTeam() { + const current: TeamMember[] = [] + const acknowledged: TeamMember[] = [] + + for (const member of teamMembers as TeamMember[]) { + if (process.env.SLACK_API_TOKEN) { + const formData = new FormData() + formData.append('token', process.env.SLACK_API_TOKEN) + formData.append('user', member.slackId) + + const slackData = await fetch( + `https://hackclub.slack.com/api/users.profile.get?user=${member.slackId}`, + { + method: 'POST', + headers: { + 'content-type': 'multipart/form-data', + cookie: process.env.SLACK_API_COOKIE || '' + }, + body: formData + } + ).then(r => r.json()) + + if (slackData.ok) { + member.pronouns = slackData.profile.pronouns + } else { + console.warn('Not found:', member.slackId) + } + } + + if (member.acknowledged) { + acknowledged.push(member) + } else { + current.push(member) + } + } + + return { current, acknowledged } +} + +export default async function handler( + _req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json(await fetchTeam()) +} diff --git a/pages/ship.js b/pages/ship.js index 0bb747b7..7c9620cc 100644 --- a/pages/ship.js +++ b/pages/ship.js @@ -168,5 +168,8 @@ export const getStaticProps = async () => { ) .then(posts => orderBy(posts, 'postedAt', 'desc')) .then(posts => take(posts, 24)) + .catch(error => { + console.log(error) + }) return { props: { posts }, revalidate: 2 } } diff --git a/pages/team.js b/pages/team.tsx similarity index 70% rename from pages/team.js rename to pages/team.tsx index 05caa8f2..91f35622 100644 --- a/pages/team.js +++ b/pages/team.tsx @@ -5,14 +5,47 @@ import Nav from '../components/nav' import Footer from '../components/footer' import Bio from '../components/bio' import ForceTheme from '../components/force-theme' -import { fetchTeam } from './api/team'; +import { fetchTeam } from './api/team' + +const CommunityTeamBox = ({ title, children }) => { + return ( + +
+ + {title} + +
+
+ {children} +
+
+ ) +} export default function Team({ team }) { return ( <> -