diff --git a/website/app/routes/_layout.agenda.($year).tsx b/website/app/routes/_layout.agenda.($year).tsx index 9bbf91a..fac35d5 100644 --- a/website/app/routes/_layout.agenda.($year).tsx +++ b/website/app/routes/_layout.agenda.($year).tsx @@ -4,15 +4,15 @@ import { useLoaderData } from '@remix-run/react' import { DateTime } from 'luxon' import { Fragment } from 'react' import { $path } from 'remix-routes' -import { Box, styled } from 'styled-system/jsx' +import { Box, Flex, styled } from 'styled-system/jsx' import { TypeOf } from 'zod' import { - ConferenceConfigYear, - ConferenceImportantInformation, - ConferenceYear, - Sponsor, - Year, - YearSponsors, + ConferenceConfigYear, + ConferenceImportantInformation, + ConferenceYear, + Sponsor, + Year, + YearSponsors, } from '~/lib/config-types' import { CACHE_CONTROL } from '~/lib/http.server' import { conferenceConfig } from '../config/conference-config' @@ -20,82 +20,96 @@ import { formatDate, getScheduleGrid, gridSmartSchema } from '../lib/sessionize. import { slugify } from '../lib/slugify' export async function loader({ params, context }: LoaderFunctionArgs) { - if (params.year && !/\d{4}/.test(params.year)) { - return redirect($path('/agenda/:year?', { year: undefined })) - } + if (params.year && !/\d{4}/.test(params.year)) { + return redirect($path('/agenda/:year?', { year: undefined })) + } - const year = - params.year && /\d{4}/.test(params.year) ? (params.year as Year) : context.conferenceState.conference.year + const year = + params.year && /\d{4}/.test(params.year) ? (params.year as Year) : context.conferenceState.conference.year - const yearConfigLookup = (conferenceConfig.conferences as Record)[year] - if (!yearConfigLookup || 'cancelledMessage' in yearConfigLookup) { - return redirect($path('/agenda/:year?', { year: undefined })) - } + const yearConfigLookup = (conferenceConfig.conferences as Record)[year] + if (!yearConfigLookup || 'cancelledMessage' in yearConfigLookup) { + return redirect($path('/agenda/:year?', { year: undefined })) + } - const yearConfig: ConferenceImportantInformation = params.year - ? getImportantInformation(yearConfigLookup) - : context.conferenceState.conference + const yearConfig: ConferenceImportantInformation = params.year + ? getImportantInformation(yearConfigLookup) + : context.conferenceState.conference - if (yearConfig.sessions?.kind === 'sessionize' && !yearConfig.sessions.sessionizeEndpoint) { - throw new Response(JSON.stringify({ message: 'No sessionize endpoint for year' }), { status: 404 }) - } + if (yearConfig.sessions?.kind === 'sessionize' && !yearConfig.sessions.sessionizeEndpoint) { + throw new Response(JSON.stringify({ message: 'No sessionize endpoint for year' }), { status: 404 }) + } - const schedules: TypeOf = - yearConfig.sessions?.kind === 'sessionize' - ? await getScheduleGrid({ - sessionizeEndpoint: yearConfig.sessions.sessionizeEndpoint, - confTimeZone: conferenceConfig.timezone, - }) - : // TODO Deal with data type - [] + const schedules: TypeOf = + yearConfig.sessions?.kind === 'sessionize' + ? await getScheduleGrid({ + sessionizeEndpoint: yearConfig.sessions.sessionizeEndpoint, + confTimeZone: conferenceConfig.timezone, + }) + : // TODO Deal with data type + [] - const schedule = schedules[0] + const schedule = schedules[0] - return json( - { - year, - sponsors: yearConfigLookup.sponsors, - conferences: Object.values(conferenceConfig.conferences).map((conf) => ({ - year: conf.year, - })), - schedule: schedule - ? { - ...schedule, - dateSlug: slugify( - formatDate(schedule.date, { - month: 'short', - day: 'numeric', - }), - ), - dateISO: schedule.date, - dateFormatted: formatDate(schedule.date, { - weekday: 'long', - month: 'long', - day: 'numeric', - }), - dateFormattedShort: formatDate(schedule.date, { - month: 'short', - day: 'numeric', - }), - } - : undefined, - }, - { headers: { 'Cache-Control': CACHE_CONTROL.conf } }, - ) + return json( + { + year, + sponsors: yearConfigLookup.sponsors, + conferences: Object.values(conferenceConfig.conferences).map((conf) => ({ + year: conf.year, + })), + schedule: schedule + ? { + ...schedule, + dateSlug: slugify( + formatDate(schedule.date, { + month: 'short', + day: 'numeric', + }), + ), + dateISO: schedule.date, + dateFormatted: formatDate(schedule.date, { + weekday: 'long', + month: 'long', + day: 'numeric', + }), + dateFormattedShort: formatDate(schedule.date, { + month: 'short', + day: 'numeric', + }), + } + : undefined, + }, + { headers: { 'Cache-Control': CACHE_CONTROL.conf } }, + ) } export default function Agenda() { - const { schedule, sponsors, conferences } = useLoaderData() + const { schedule, sponsors, conferences } = useLoaderData() - if (!schedule) { - return Agenda has not been announced - } - return ( - - Agenda has not been announced + } + return ( + + `[time-${timeSlot.slotStart.replace(/:/g, '')}] 1fr`, - ), - ].join(' '), - '--room-columns': [ - '[times] auto', - ...schedule.rooms.map((room, index, rooms) => - index === 0 - ? `[room-${room.id}-start] 1fr` - : index + 1 === rooms.length - ? `[room-${rooms[index - 1].id}-end room-${room.id}-start] 1fr [room-${room.id}-end]` - : `[room-${rooms[index - 1].id}-end room-${room.id}-start] 1fr`, - ), - ].join(' '), - } as React.CSSProperties - } - md={{ - display: 'grid', - gridTemplateRows: 'var(--slot-rows)', - gridTemplateColumns: 'var(--room-columns)', - gridGap: '1', - }} - > - {schedule.rooms.map((room) => ( - /* hidden on small screens and browsers without grid support */ - - ))} + '--slot-rows': [ + '[rooms] auto', + ...schedule.timeSlots.map((timeSlot) => `[time-${timeSlot.slotStart.replace(/:/g, '')}] 1fr`), + ].join(' '), + '--room-columns': [ + '[times] auto', + ...schedule.rooms.map((room, index, rooms) => + index === 0 + ? `[room-${room.id}-start] 1fr` + : index + 1 === rooms.length + ? `[room-${rooms[index - 1].id}-end room-${room.id}-start] 1fr [room-${room.id}-end]` + : `[room-${rooms[index - 1].id}-end room-${room.id}-start] 1fr`, + ), + ].join(' '), + } as React.CSSProperties + } + md={{ + display: 'grid', + gridTemplateRows: 'var(--slot-rows)', + gridTemplateColumns: 'var(--room-columns)', + gridGap: '1', + }} + > + {schedule.rooms.map((room) => ( + /* hidden on small screens and browsers without grid support */ + + ))} - {schedule.timeSlots.map((timeSlot) => { - const startTime12 = DateTime.fromISO(timeSlot.slotStart).toFormat('h:mm a').toLowerCase() - const timeSlotSimple = timeSlot.slotStart.replace(/:/g, '') + {schedule.timeSlots.map((timeSlot) => { + const startTime12 = DateTime.fromISO(timeSlot.slotStart).toFormat('h:mm a').toLowerCase() + const timeSlotSimple = timeSlot.slotStart.replace(/:/g, '') - return ( - - - {startTime12} - + return ( + + + {startTime12} + - {timeSlot.rooms.map((room) => { - const fullSession = schedule.rooms - .find((r) => r.id === room.id) - ?.sessions.find((session) => session.id === room.session.id) - const endsAtTime = fullSession?.endsAt.replace(/\d{4}-\d{2}-\d{2}T/, '') - const endTime12 = fullSession?.endsAt - ? DateTime.fromISO(fullSession.endsAt).toFormat('h:mm a').toLowerCase() - : undefined + {timeSlot.rooms.map((room) => { + const fullSession = schedule.rooms + .find((r) => r.id === room.id) + ?.sessions.find((session) => session.id === room.session.id) + const endsAtTime = fullSession?.endsAt.replace(/\d{4}-\d{2}-\d{2}T/, '') + const endTime12 = fullSession?.endsAt + ? DateTime.fromISO(fullSession.endsAt).toFormat('h:mm a').toLowerCase() + : undefined - return ( - - - {fullSession?.title} - - - {startTime12} - {endTime12} - - {room.name} - - {fullSession?.speakers.map((speaker) => speaker.name)?.join(', ')} - - - ) - })} - - ) - })} - + return ( + + + + {fullSession?.title} + + + 🕓 {startTime12} - {endTime12} + + + 📍 {room.name} + + {fullSession?.speakers.length > 0 && ( + + 💬 {fullSession?.speakers.map((speaker) => speaker.name)?.join(', ')} + + )} + + + ) + })} + + ) + })} + - + - - - ) + + + ) } function ConferenceBrowser({ conferences }: { conferences: { year: Year }[] }) { - return ( - - - Other Conferences - - - {conferences.map((conf) => ( - - {conf.year} - - ))} - - - ) + return ( + + + Other Conferences + + + {conferences.map((conf) => ( + + {conf.year} + + ))} + + + ) } function SponsorSection({ sponsors }: { sponsors: YearSponsors | undefined }) { - const renderSponsorCategory = ( - title: string, - sponsors: Sponsor[] | undefined, - logoSize: 'xs' | 'sm' | 'md' | 'lg', - ) => { - if (!sponsors || sponsors.length === 0) return null - - return ( - - - {title} - - - {sponsors.map((sponsor) => ( - - - - ))} - - - ) - } - - if (!sponsors) return null + const renderSponsorCategory = ( + title: string, + sponsors: Sponsor[] | undefined, + logoSize: 'xs' | 'sm' | 'md' | 'lg', + ) => { + if (!sponsors || sponsors.length === 0) return null return ( - - {renderSponsorCategory('Platinum Sponsors', sponsors.platinum, 'lg')} - {renderSponsorCategory('Gold Sponsors', sponsors.gold, 'md')} - {renderSponsorCategory('Silver Sponsors', sponsors.silver, 'sm')} - {renderSponsorCategory('Bronze Sponsors', sponsors.bronze, 'xs')} - {renderSponsorCategory('Community Sponsors', sponsors.community, 'xs')} - {renderSponsorCategory('Coffee Cart Sponsors', sponsors.coffeeCart, 'xs')} - {renderSponsorCategory('Quiet Room Sponsors', sponsors.quietRoom, 'xs')} - {renderSponsorCategory('Keynote Sponsors', sponsors.keynotes, 'sm')} + + + {title} + + + {sponsors.map((sponsor) => ( + + + + ))} + ) + } + + if (!sponsors) return null + + return ( + + {renderSponsorCategory('Platinum Sponsors', sponsors.platinum, 'lg')} + {renderSponsorCategory('Gold Sponsors', sponsors.gold, 'md')} + {renderSponsorCategory('Silver Sponsors', sponsors.silver, 'sm')} + {renderSponsorCategory('Bronze Sponsors', sponsors.bronze, 'xs')} + {renderSponsorCategory('Community Sponsors', sponsors.community, 'xs')} + {renderSponsorCategory('Coffee Cart Sponsors', sponsors.coffeeCart, 'xs')} + {renderSponsorCategory('Quiet Room Sponsors', sponsors.quietRoom, 'xs')} + {renderSponsorCategory('Keynote Sponsors', sponsors.keynotes, 'sm')} + + ) } function getImportantInformation(yearConfig: ConferenceYear): ConferenceImportantInformation { - return { - date: yearConfig.conferenceDate?.toISO(), - year: yearConfig.year, - sessions: yearConfig.sessions, - ticketPrice: yearConfig.ticketPrice, - } + return { + date: yearConfig.conferenceDate?.toISO(), + year: yearConfig.year, + sessions: yearConfig.sessions, + ticketPrice: yearConfig.ticketPrice, + } }