From f496074e282cdd622dd21fe2e97415c84112eb9d Mon Sep 17 00:00:00 2001 From: Jake Ginnivan Date: Tue, 20 Aug 2024 22:20:31 +0800 Subject: [PATCH] Fix sponsor on talk page --- .../page-components/SponsorSection.tsx | 116 +++ .../_layout.agenda.$year.talk.$sessionId.tsx | 64 +- website/app/routes/_layout.agenda.($year).tsx | 716 ++++++++---------- 3 files changed, 430 insertions(+), 466 deletions(-) create mode 100644 website/app/components/page-components/SponsorSection.tsx diff --git a/website/app/components/page-components/SponsorSection.tsx b/website/app/components/page-components/SponsorSection.tsx new file mode 100644 index 0000000..90e18fa --- /dev/null +++ b/website/app/components/page-components/SponsorSection.tsx @@ -0,0 +1,116 @@ +import { Flex, styled } from 'styled-system/jsx' +import { Sponsor, Year, YearSponsors } from '~/lib/config-types' + +export function SponsorSection({ sponsors, year }: { sponsors: YearSponsors | undefined; year: Year }) { + const sponsorStyles = { + platinum: { gradientFrom: '#496F7F', logoSize: 'lg' }, + gold: { gradientFrom: '#453927', logoSize: 'md' }, + silver: { gradientFrom: '#2A3251', logoSize: 'sm' }, + bronze: { gradientFrom: '#452927', logoSize: 'xs' }, + digital: { gradientFrom: '#371F4E', logoSize: 'xs' }, + community: { gradientFrom: '#1F1F4E', logoSize: 'xs' }, + coffeeCart: { gradientFrom: '#1F1F4E', logoSize: 'xs' }, + quietRoom: { gradientFrom: '#1F1F4E', logoSize: 'xs' }, + keynotes: { gradientFrom: '#1F1F4E', logoSize: 'sm' }, + } as const + const getSponsorStyle = (category: keyof typeof sponsorStyles) => sponsorStyles[category] + + const renderSponsor = (sponsor: Sponsor, category: keyof typeof sponsorStyles, zIndex: number) => { + const { gradientFrom } = getSponsorStyle(category) + + return ( + + + + {category.charAt(0).toUpperCase() + category.slice(1)} Sponsor + + + ) + } + + const renderSponsorGroup = ( + title: string, + sponsorGroups: { sponsors: Sponsor[] | undefined; category: keyof typeof sponsorStyles }[], + ) => { + const allSponsors = sponsorGroups.flatMap((group) => group.sponsors || []) + if (allSponsors.length === 0) return null + + let zIndex = allSponsors.length + + return ( + + + {title} + + + {sponsorGroups.map((group) => + group.sponsors?.map((sponsor) => { + const sponsorElement = renderSponsor(sponsor, group.category, zIndex) + zIndex -= 1 + return sponsorElement + }), + )} + + + ) + } + + if (!sponsors) return null + + return ( + + + {year} Sponsors + + {renderSponsorGroup('Major Sponsors', [ + { sponsors: sponsors.platinum, category: 'platinum' }, + { sponsors: sponsors.gold, category: 'gold' }, + { sponsors: sponsors.silver, category: 'silver' }, + { sponsors: sponsors.bronze, category: 'bronze' }, + ])} + {renderSponsorGroup('Minor Sponsors', [ + { sponsors: sponsors.digital, category: 'digital' }, + { sponsors: sponsors.community, category: 'community' }, + { sponsors: sponsors.coffeeCart, category: 'coffeeCart' }, + { sponsors: sponsors.quietRoom, category: 'quietRoom' }, + { sponsors: sponsors.keynotes, category: 'keynotes' }, + ])} + + ) +} diff --git a/website/app/routes/_layout.agenda.$year.talk.$sessionId.tsx b/website/app/routes/_layout.agenda.$year.talk.$sessionId.tsx index 54458a3..8f69c47 100644 --- a/website/app/routes/_layout.agenda.$year.talk.$sessionId.tsx +++ b/website/app/routes/_layout.agenda.$year.talk.$sessionId.tsx @@ -6,14 +6,8 @@ import { $params, $path } from 'remix-routes' import { Box, Flex, styled } from 'styled-system/jsx' import { TypeOf } from 'zod' import { AppLink } from '~/components/app-link' -import { - ConferenceConfigYear, - ConferenceImportantInformation, - ConferenceYear, - Sponsor, - Year, - YearSponsors, -} from '~/lib/config-types' +import { SponsorSection } from '~/components/page-components/SponsorSection' +import { ConferenceConfigYear, ConferenceImportantInformation, ConferenceYear, Year } from '~/lib/config-types' import { localeTimeFormat } from '~/lib/dates/formatting' import { CACHE_CONTROL } from '~/lib/http.server' import { conferenceConfig } from '../config/conference-config' @@ -134,60 +128,6 @@ function ConferenceBrowser({ conferences }: { conferences: { year: Year }[] }) { ) } -function SponsorSection({ sponsors, year }: { sponsors: YearSponsors | undefined; year: Year }) { - const renderSponsorCategory = ( - title: string, - sponsors: Sponsor[] | undefined, - logoSize: 'xs' | 'sm' | 'md' | 'lg', - ) => { - if (!sponsors || sponsors.length === 0) return null - - const maxLogoSize = - logoSize === 'lg' ? '250px' : logoSize === 'md' ? '150px' : logoSize === 'sm' ? '100px' : '75px' - return ( - - - {title} - - - {sponsors.map((sponsor) => ( - - - - ))} - - - ) - } - - if (!sponsors) return null - - return ( - - - {year} Sponsors - - {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('Digital Sponsors', sponsors.digital, '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(), diff --git a/website/app/routes/_layout.agenda.($year).tsx b/website/app/routes/_layout.agenda.($year).tsx index f2fd84f..7339352 100644 --- a/website/app/routes/_layout.agenda.($year).tsx +++ b/website/app/routes/_layout.agenda.($year).tsx @@ -6,127 +6,122 @@ import { Fragment } from 'react' import { $path } from 'remix-routes' import { Box, Flex, styled } from 'styled-system/jsx' import { TypeOf } from 'zod' -import { - ConferenceConfigYear, - ConferenceImportantInformation, - ConferenceYear, - Sponsor, - Year, - YearSponsors, -} from '~/lib/config-types' +import { AppLink } from '~/components/app-link' +import { ConferenceConfigYear, ConferenceImportantInformation, ConferenceYear, Year } from '~/lib/config-types' import { localeTimeFormat } from '~/lib/dates/formatting' import { CACHE_CONTROL } from '~/lib/http.server' +import { SponsorSection } from '../components/page-components/SponsorSection' import { conferenceConfig } from '../config/conference-config' import { formatDate, getScheduleGrid, gridSmartSchema } from '../lib/sessionize.server' 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) { - if (!params.year) { - throw new Response(JSON.stringify({ message: 'No config for year' }), { status: 404 }) - } + const yearConfigLookup = (conferenceConfig.conferences as Record)[year] + if (!yearConfigLookup || 'cancelledMessage' in yearConfigLookup) { + if (!params.year) { + throw new Response(JSON.stringify({ message: 'No config for year' }), { status: 404 }) + } - return redirect($path('/agenda/:year?', { year: undefined })) - } + 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, year } = useLoaderData() - const availableTimeSlots = schedule?.timeSlots.map((timeSlot) => timeSlot.slotStart.replace(/:/g, '')) + const { schedule, sponsors, conferences, year } = useLoaderData() + const availableTimeSlots = schedule?.timeSlots.map((timeSlot) => timeSlot.slotStart.replace(/:/g, '')) - if (!schedule) { - return ( - -

- {conferenceConfig.name} {year} agenda has not been announced -

+ if (!schedule) { + return ( + +

+ {conferenceConfig.name} {year} agenda has not been announced +

- + - -
- ) - } + +
+ ) + } - return ( - - + `[time-${timeSlot.slotStart.replace(/:/g, '')}] auto`), - ].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, timeSlotIndex) => { - const startTime12 = DateTime.fromISO(timeSlot.slotStart).toFormat('h:mm a').toLowerCase() - const timeSlotSimple = timeSlot.slotStart.replace(/:/g, '') - const nextTimeSlot = schedule.timeSlots[timeSlotIndex + 1] - const nextTimeSlotStart = nextTimeSlot?.slotStart.replace(/:/g, '') - - return ( - - - {startTime12} - {nextTimeSlot?.slotStart ? ( - - {' '} - - {DateTime.fromISO(nextTimeSlot.slotStart).toFormat('h:mm a').toLowerCase()} - - ) : null} - - - {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 - - const timeSlotEnd = endsAtTime?.replace(/:/g, '') ?? '' - const earliestEnd = !availableTimeSlots?.includes(timeSlotEnd) - ? nextTimeSlotStart - : timeSlotEnd ?? nextTimeSlotStart - - return ( - - - - {fullSession?.title} - - `[time-${timeSlot.slotStart.replace(/:/g, '')}] auto`, + ), + ].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 */ + - - - - + > {room.name} - - {fullSession?.speakers?.length ? ( - - - - - {fullSession?.speakers.map((speaker) => speaker.name)?.join(', ')} - - ) : null} - - - ) - })} - - ) - })} - - - + + ))} - - - ) -} - -function ConferenceBrowser({ conferences }: { conferences: { year: Year }[] }) { - return ( - - - Other Conferences - - - {conferences.map((conf) => ( - - {conf.year} - - ))} - - - ) -} + {schedule.timeSlots.map((timeSlot, timeSlotIndex) => { + const startTime12 = DateTime.fromISO(timeSlot.slotStart).toFormat('h:mm a').toLowerCase() + const timeSlotSimple = timeSlot.slotStart.replace(/:/g, '') + const nextTimeSlot = schedule.timeSlots[timeSlotIndex + 1] + const nextTimeSlotStart = nextTimeSlot?.slotStart.replace(/:/g, '') -function SponsorSection({ sponsors, year }: { sponsors: YearSponsors | undefined; year: Year }) { - const sponsorStyles = { - platinum: { gradientFrom: '#496F7F', logoSize: 'lg' as 'lg' }, - gold: { gradientFrom: '#453927', logoSize: 'md' as 'md' }, - silver: { gradientFrom: '#2A3251', logoSize: 'sm' as 'sm' }, - bronze: { gradientFrom: '#452927', logoSize: 'xs' as 'xs' }, - digital: { gradientFrom: '#371F4E', logoSize: 'xs' as 'xs' }, - community: { gradientFrom: '#1F1F4E', logoSize: 'xs' as 'xs' }, - coffeeCart: { gradientFrom: '#1F1F4E', logoSize: 'xs' as 'xs' }, - quietRoom: { gradientFrom: '#1F1F4E', logoSize: 'xs' as 'xs' }, - keynotes: { gradientFrom: '#1F1F4E', logoSize: 'sm' as 'sm' }, - } - const getSponsorStyle = (category: keyof typeof sponsorStyles) => sponsorStyles[category] + return ( + + + {startTime12} + {nextTimeSlot?.slotStart ? ( + + {' '} + - {DateTime.fromISO(nextTimeSlot.slotStart).toFormat('h:mm a').toLowerCase()} + + ) : null} + - const renderSponsor = (sponsor: Sponsor, category: keyof typeof sponsorStyles, zIndex: number) => { - const { gradientFrom, logoSize } = getSponsorStyle(category) + {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 ( - - - - {category.charAt(0).toUpperCase() + category.slice(1)} Sponsor - - - ) - } + const timeSlotEnd = endsAtTime?.replace(/:/g, '') ?? '' + const earliestEnd = !availableTimeSlots?.includes(timeSlotEnd) + ? nextTimeSlotStart + : (timeSlotEnd ?? nextTimeSlotStart) - const renderSponsorGroup = ( - title: string, - sponsorGroups: { sponsors: Sponsor[] | undefined; category: keyof typeof sponsorStyles }[], - ) => { - const allSponsors = sponsorGroups.flatMap((group) => group.sponsors || []) - if (allSponsors.length === 0) return null + return ( + + + + + {fullSession?.title} + + + + + + + {startTime12} - {endTime12} + + + + + + {room.name} + + {fullSession?.speakers?.length ? ( + + + + + {fullSession?.speakers.map((speaker) => speaker.name)?.join(', ')} + + ) : null} + + + ) + })} + + ) + })} + - let zIndex = allSponsors.length + - return ( - - - {title} - - - {sponsorGroups.map((group) => - group.sponsors?.map((sponsor) => { - const sponsorElement = renderSponsor(sponsor, group.category, zIndex) - zIndex -= 1 - return sponsorElement - }), - )} + - ) - } - - if (!sponsors) return null +} - return ( - - - {year} Sponsors - - {renderSponsorGroup('Major Sponsors', [ - { sponsors: sponsors.platinum, category: 'platinum' }, - { sponsors: sponsors.gold, category: 'gold' }, - { sponsors: sponsors.silver, category: 'silver' }, - { sponsors: sponsors.bronze, category: 'bronze' }, - ])} - {renderSponsorGroup('Minor Sponsors', [ - { sponsors: sponsors.digital, category: 'digital' }, - { sponsors: sponsors.community, category: 'community' }, - { sponsors: sponsors.coffeeCart, category: 'coffeeCart' }, - { sponsors: sponsors.quietRoom, category: 'quietRoom' }, - { sponsors: sponsors.keynotes, category: 'keynotes' }, - ])} - - ) +function ConferenceBrowser({ conferences }: { conferences: { year: Year }[] }) { + return ( + + + Other Conferences + + + {conferences.map((conf) => ( + + {conf.year} + + ))} + + + ) } function getImportantInformation(yearConfig: ConferenceYear): ConferenceImportantInformation { - return { - date: yearConfig.conferenceDate?.toISO(), - year: yearConfig.year, - sessions: yearConfig.sessions, - ticketPrice: yearConfig.ticketPrice, - votingOpens: yearConfig.talkVotingDates?.opens.toLocaleString(localeTimeFormat), - } + return { + date: yearConfig.conferenceDate?.toISO(), + year: yearConfig.year, + sessions: yearConfig.sessions, + ticketPrice: yearConfig.ticketPrice, + votingOpens: yearConfig.talkVotingDates?.opens.toLocaleString(localeTimeFormat), + } }