diff --git a/components/event.js b/components/event.js index 4bf4ed0..d4c26bf 100644 --- a/components/event.js +++ b/components/event.js @@ -2,6 +2,7 @@ import { Box, Text, Flex, Avatar, Heading } from 'theme-ui' import tt from 'tinytime' import Link from 'next/link' import Sparkles from './sparkles' +import { XCircle, Users } from 'react-feather' const past = dt => new Date(dt) < new Date() const now = (start, end) => @@ -22,29 +23,66 @@ const tagStyle = { mt: 1 } -const Event = ({ id, slug, title, desc, leader, avatar, start, end, cal, tags }) => ( +const cancelledStyle = { + display: 'inline-flex', + alignItems: 'center', + gap: '4px', + fontSize: 0, + fontWeight: 'bold', + textTransform: 'uppercase', + letterSpacing: '0.05rem', + bg: 'red', + color: 'white', + px: 2, + py: '2px', + borderRadius: 'default', + mr: 1, + mt: 1 +} + +const Event = ({ + id, + slug, + title, + desc, + leader, + avatar, + cancelled, + interestCount, + start, + end, + cal, + tags +}) => ( @@ -56,6 +94,15 @@ const Event = ({ id, slug, title, desc, leader, avatar, start, end, cal, tags }) {title} + {cancelled && ( + + + {' '} + + Cancelled + + + )} {tags?.length > 0 && ( {tags.map(tag => ( @@ -65,6 +112,7 @@ const Event = ({ id, slug, title, desc, leader, avatar, start, end, cal, tags }) ))} )} + )} {leader} + {interestCount > 0 && ( + + {interestCount} + + )} - {now(start, end) && ( + {now(start, end) && !cancelled && ( ( ( sx={{ color: 'primary', borderRadius: 'circle', + cursor: 'pointer', + m: 2, transition: 'box-shadow .125s ease-in-out', ':hover,:focus': { boxShadow: '0 0 0 2px', @@ -51,13 +61,26 @@ const Flag = () => ( const ColorSwitcher = props => { const [mode, setMode] = useColorMode() + const [mounted, setMounted] = useState(false) + useEffect(() => { + setMounted(true) + }, []) return ( setMode(mode === 'dark' ? 'light' : 'dark')} title="Reverse color scheme" + sx={{ + ...props.sx, + transition: 'transform 0.3s ease,box-shadow .125s ease-in-out' + }} > - + {mounted && + (mode === 'dark' ? ( + + ) : ( + + ))} ) } @@ -65,13 +88,12 @@ const ColorSwitcher = props => { export default () => { const [mode] = useColorMode() const router = useRouter() - const [session, setSession] = useState(null) useEffect(() => { fetch('/api/auth/me/') - .then(r => r.json()) - .then(data => setSession(data)) - .catch(() => setSession({ slackId: null })) + .then(r => r.json()) + .then(data => setSession(data)) + .catch(() => setSession({ slackId: null })) }, []) const home = router.pathname === '/' @@ -107,7 +129,7 @@ export default () => { {session?.slackId ? ( - + { src={`https://cachet.dunkirk.sh/users/${session.slackId}/r`} alt="Your Slack Avatar" size={28} - sx={{hieght:28, width:28, borderRadius:'circle'}} + sx={{ height: 28, width: 28, borderRadius: 'circle' }} /> - ): session != null ? ( + ) : session != null ? ( Log in - ): null} + ) : null} ) diff --git a/lib/data.js b/lib/data.js index fb2b1c0..143c681 100644 --- a/lib/data.js +++ b/lib/data.js @@ -86,6 +86,10 @@ export const getEvents = async (page = 1) => { avatar: e['Avatar'], approved: e['Approved'] || false, tags: e['Tags'] || [], + cancelled: e['Cancelled'] || false, + rawCancellation: e['RawCancellation'] || null, + interestCount: + typeof e['InterestCount'] === 'number' ? e['InterestCount'] : 0, interestCount: e['InterestCount'] || 0, leaderSlackId: e['LeaderSlackID'], rsvpFormUrl: e['RSVPFormURL'] || null diff --git a/pages/[slug].js b/pages/[slug].js index bfb8b34..3494b75 100644 --- a/pages/[slug].js +++ b/pages/[slug].js @@ -11,7 +11,14 @@ import { Spinner, Text } from 'theme-ui' -import { Calendar, Download, Youtube } from 'react-feather' +import { + Calendar, + Download, + Youtube, + XCircle, + Users, + AlertTriangle +} from 'react-feather' import { useRouter } from 'next/router' import Head from 'next/head' import Meta from '@hackclub/meta' @@ -41,7 +48,11 @@ const makeICS = event => { const location = calUrl?.searchParams.get('location') || '' const escICS = str => - str.replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n') + str + .replace(/\\/g, '\\\\') + .replace(/;/g, '\\;') + .replace(/,/g, '\\,') + .replace(/\n/g, '\\n') const lines = [ 'BEGIN:VCALENDAR', @@ -76,6 +87,27 @@ const Page = ({ event }) => ( event.avatar }`} /> + {event.cancelled && ( + + + + + + This event has been cancelled + + + {event.rawCancellation && ( + + {event.rawCancellation} + + )} + + + )} @@ -155,8 +187,43 @@ const Page = ({ event }) => ( {tt('{h}:{mm} {a}').render(new Date(event.start))}– {tt('{h}:{mm} {a}').render(new Date(event.end))} + {event.interestCount > 0 && ( + + + + {event.interestCount}{' '} + {event.interestCount === 1 ? 'person is' : 'people are'}{' '} + interested + + + )} - {event.tags?.length > 0 && ( + {event.cancelled && ( + + + CANCELLED + + + )} + {event.tags?.length > 0 && !event.cancelled && ( {event.tags.map(tag => ( ( - {!past(event.start) && ( + {!past(event.start) && !event.cancelled && ( - @@ -205,20 +267,15 @@ const Page = ({ event }) => ( )} - { !event.ama && ( - event.rsvpFormUrl - ? - : - )} - {event.ama && ( + {(event.youtube || event.ama) && ( ( } py={[4, 5]} > - {past(event.start) || event.youtube ? ( + {past(event.start) && event.youtube && ( <> - {event.youtube && ( - - - - )} + + + - ) : null} - {!past(event.start) && ( + )} + + {!past(event.start) && event.ama && ( ( )} + {!past(event.start) && !event.ama && event.youtube && ( + + + This event will be livestreamed on Youtube + + + )} )} @@ -410,6 +473,9 @@ export const getStaticProps = async ({ params }) => { const { slug } = params const events = await getEvents() const event = find(events, { slug }) + if (!event) { + return { notFound: true } + } event.html = await parse(event.desc) event.desc ??= null return { props: { event }, revalidate: 2 }