Skip to content

Commit 589d8b4

Browse files
committed
Improve how session description looks like
1 parent acda971 commit 589d8b4

File tree

5 files changed

+82
-116
lines changed

5 files changed

+82
-116
lines changed

src/app/conf/2025/_data.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,24 @@ async function getSchedule(): Promise<ScheduleSession[]> {
8484
// TODO: Preserve formatting??
8585
return {
8686
...session,
87-
description: description && stripHtml(description).result,
87+
description: preprocessDescription(description),
8888
}
8989
})
9090

9191
return result
9292
}
9393

94+
function preprocessDescription(description: string | undefined | null): string {
95+
let res = description || ""
96+
97+
// we respect manual line breaks
98+
res = res.replace(/<br\s*\/?>/g, "\n")
99+
100+
// respecting <li> and <a> tags doesn't make sense, because speakers don't use them consistently
101+
// we'll improve how the descriptions look later down the tree in the session details page
102+
return stripHtml(res).result
103+
}
104+
94105
export const speakers = await getSpeakers()
95106

96107
// TODO: Collect tags from schedule for speakers.

src/app/conf/2025/components/session-tags.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
import clsx from "clsx"
2+
import React from "react"
3+
14
import { ScheduleSession } from "@/app/conf/2023/types"
25
import { Tag } from "@/app/conf/_design-system/tag"
3-
import React from "react"
46

57
import { eventsColors } from "../utils"
68

7-
export function SessionTags({ session }: { session: ScheduleSession }) {
9+
export interface SessionTagsProps extends React.HTMLAttributes<HTMLDivElement> {
10+
session: ScheduleSession
11+
className?: string
12+
}
13+
14+
export function SessionTags({ session, className, ...rest }: SessionTagsProps) {
815
const eventType = session.event_type.endsWith("s")
916
? session.event_type.slice(0, -1)
1017
: session.event_type
1118

1219
return (
13-
<div className="flex flex-wrap gap-3">
20+
<div className={clsx("flex flex-wrap gap-3", className)} {...rest}>
1421
{eventType && (
1522
<Tag color={eventsColors[session.event_type]}>{eventType}</Tag>
1623
)}

src/app/conf/2025/schedule/[id]/page.tsx

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,22 @@ export function generateStaticParams() {
4949
}
5050

5151
export default function SessionPage({ params }: SessionProps) {
52-
const event = schedule.find(s => s.id === params.id)
53-
if (!event) {
52+
const session = schedule.find(s => s.id === params.id)
53+
if (!session) {
5454
notFound()
5555
}
5656

5757
// @ts-expect-error -- fixme
58-
event.speakers = (event.speakers || []).map(speaker =>
58+
session.speakers = (session.speakers || []).map(speaker =>
5959
speakers.find(s => s.username === speaker.username),
6060
)
6161

6262
const eventTitle = getEventTitle(
63-
event,
64-
event.speakers!.map(s => s.name),
63+
session,
64+
session.speakers!.map(s => s.name),
6565
)
6666

67-
const video = findVideo(event, eventTitle)
67+
const video = findVideo(session, eventTitle)
6868

6969
return (
7070
<>
@@ -75,7 +75,7 @@ export default function SessionPage({ params }: SessionProps) {
7575
<div className="gql-conf-section !py-0">
7676
<div className="border-x border-neu-200 pt-8 dark:border-neu-100 2xl:pt-16">
7777
<SessionHeader
78-
event={event}
78+
event={session}
7979
eventTitle={eventTitle}
8080
year="2025"
8181
className={clsx(
@@ -89,14 +89,9 @@ export default function SessionPage({ params }: SessionProps) {
8989
<Hr className="mt-10 2xl:mt-16" />
9090
)}
9191

92-
{event.description && (
92+
{session.description && (
9393
<>
94-
<div className="mt-8 flex gap-4 px-2 pb-8 max-lg:flex-col sm:px-3 lg:mt-16 lg:gap-8 xl:pb-16">
95-
<h3 className="typography-h2 min-w-[320px]">
96-
Session description
97-
</h3>
98-
<p className="typography-body-lg">{event.description}</p>
99-
</div>
94+
<SessionDescription session={session} />
10095
<Hr />
10196
</>
10297
)}
@@ -105,19 +100,19 @@ export default function SessionPage({ params }: SessionProps) {
105100
Session speakers
106101
</h3>
107102
<SessionSpeakers
108-
event={event}
103+
session={session}
109104
className="-mx-px -mb-px last:xl:pb-24"
110105
/>
111106

112-
{!!event.files?.length && (
107+
{!!session.files?.length && (
113108
<>
114109
<Hr />
115110

116111
<h3 className="typography-h2 my-8 px-2 sm:px-3 lg:my-16">
117112
Session resources
118113
</h3>
119114
<section>
120-
{event.files?.map(({ path }) => (
115+
{session.files?.map(({ path }) => (
121116
<iframe
122117
key={path}
123118
src={path}
@@ -191,9 +186,12 @@ function SessionHeader({
191186
<div className="typography-body-md flex flex-col gap-4 md:flex-row md:gap-6">
192187
<div className="flex items-center gap-2">
193188
<CalendarIcon className="size-5 text-sec-darker dark:text-sec-light/90 sm:size-6" />
194-
<time dateTime="2025-09-08">September 08</time>
195-
<span>-</span>
196-
<time dateTime="2025-09-10">10, 2025</time>
189+
<time dateTime={event.event_start}>
190+
{new Date(event.event_start).toLocaleDateString("en-US", {
191+
day: "numeric",
192+
month: "long",
193+
})}
194+
</time>
197195
</div>
198196
<div className="flex items-center gap-2">
199197
<PinIcon className="size-5 text-sec-darker dark:text-sec-light/90 sm:size-6" />
@@ -207,10 +205,10 @@ function SessionHeader({
207205
}
208206

209207
function SessionSpeakers({
210-
event,
208+
session: event,
211209
className,
212210
}: {
213-
event: ScheduleSession
211+
session: ScheduleSession
214212
className?: string
215213
}) {
216214
return (
@@ -237,3 +235,16 @@ function Hr({ className }: { className?: string }) {
237235
/>
238236
)
239237
}
238+
239+
function SessionDescription({ session }: { session: ScheduleSession }) {
240+
241+
242+
return (
243+
<div className="mt-8 flex gap-4 px-2 pb-8 max-lg:flex-col sm:px-3 lg:mt-16 lg:gap-8 xl:pb-16">
244+
<h3 className="typography-h2 min-w-[320px]">Session description</h3>
245+
<p className="typography-body-lg whitespace-pre-wrap">
246+
{session.description}
247+
</p>
248+
</div>
249+
)
250+
}

src/app/conf/2025/speakers/[id]/long-session-card.tsx

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import PlayIcon from "@/app/conf/_design-system/pixelarticons/play.svg?svgr"
1111
import { findVideo } from "../../schedule/[id]/session-video"
1212
import { eventsColors, getEventTitle } from "../../utils"
1313
import React from "react"
14+
import { SessionTags } from "../../components/session-tags"
1415

1516
export interface LongSessionCardProps
1617
extends React.HTMLAttributes<HTMLDivElement> {
@@ -19,41 +20,35 @@ export interface LongSessionCardProps
1920
year?: string
2021
}
2122

22-
function formatTime(dateString: string): string {
23-
return new Date(dateString).toLocaleTimeString("en-US", {
24-
hour: "2-digit",
25-
minute: "2-digit",
26-
hour12: false,
27-
})
28-
}
29-
30-
function formatDate(dateString: string): string {
31-
return new Date(dateString).toLocaleDateString("en-US", {
32-
day: "numeric",
33-
month: "long",
34-
})
35-
}
36-
3723
export function LongSessionCard({
3824
session,
3925
year = "2025",
4026
className,
4127
...props
4228
}: LongSessionCardProps) {
43-
const eventType = session.event_type.endsWith("s")
44-
? session.event_type.slice(0, -1)
45-
: session.event_type
46-
47-
const formattedDate = formatDate(session.event_start)
48-
const formattedTime = formatTime(session.event_start)
29+
const formattedDate = new Date(session.event_start).toLocaleDateString(
30+
"en-US",
31+
{
32+
day: "numeric",
33+
month: "long",
34+
},
35+
)
36+
const formattedTime = new Date(session.event_start).toLocaleTimeString(
37+
"en-US",
38+
{
39+
hour: "2-digit",
40+
minute: "2-digit",
41+
hour12: false,
42+
},
43+
)
4944

5045
const eventTitle = getEventTitle(
5146
session,
5247
session.speakers?.map(s => s.name) || [],
5348
)
5449
const video = findVideo(session, eventTitle)
5550

56-
const eventDuration =
51+
const eventDurationMs =
5752
new Date(session.event_end).getTime() -
5853
new Date(session.event_start).getTime()
5954

@@ -74,15 +69,9 @@ export function LongSessionCard({
7469
aria-label={`Read more about "${eventTitle}" by ${session.speakers?.[0]?.name || "Speaker"}`}
7570
/>
7671

77-
<div className={clsx("flex flex-col gap-6", video && "mb-6")}>
72+
<div className="flex flex-col gap-6">
7873
<div className="flex items-center justify-between gap-6">
79-
<Tag
80-
color={
81-
eventsColors[session.event_type] || "hsl(var(--color-neu-700))"
82-
}
83-
>
84-
{eventType}
85-
</Tag>
74+
<SessionTags session={session} />
8675
{video && (
8776
<div className="flex items-center gap-2 border border-neu-400 bg-neu-100 px-2 py-1">
8877
<span className="typography-menu text-neu-900">
@@ -93,7 +82,7 @@ export function LongSessionCard({
9382
)}
9483
</div>
9584

96-
<div className="flex flex-col gap-2">
85+
<div className="flex flex-col gap-4">
9786
<div className="min-h-[120px]">
9887
<h3 className="typography-h3 text-neu-900">{session.name}</h3>
9988
</div>
@@ -121,14 +110,16 @@ export function LongSessionCard({
121110
<div className="flex items-center gap-0.5">
122111
<ClockIcon className="size-3" />
123112
<span className="typography-body-xs text-neu-600">
124-
{eventDuration}
113+
{Math.round(eventDurationMs / (1000 * 60))} min
125114
</span>
126115
</div>
127116
)}
128117
</div>
129118
</div>
130119
</div>
131120

121+
{/* todo: past session no recording variant */}
122+
132123
{video ? (
133124
<Button
134125
href={`https://youtube.com/embed/${video.id}`}
@@ -139,8 +130,8 @@ export function LongSessionCard({
139130
<PlayIcon className="size-6" />
140131
</Button>
141132
) : (
142-
<div className="flex items-center text-neu-800">
143-
<div className="flex flex-1 items-center gap-6 border-r border-neu-200 pr-6">
133+
<footer className="flex items-center border-t border-neu-200 text-neu-800">
134+
<div className="flex flex-1 items-center gap-6 border-r border-neu-200 px-6 py-4">
144135
<div className="flex items-center gap-0.5">
145136
<CalendarIcon className="size-4 text-sec-dark" />
146137
<span className="typography-body-xs">{formattedDate}</span>
@@ -154,13 +145,13 @@ export function LongSessionCard({
154145
<span className="typography-body-xs">{session.venue}</span>
155146
</div>
156147
</div>
157-
<div className="flex flex-col items-center justify-center gap-6 pl-6">
148+
<div className="flex flex-col items-center justify-center gap-6 px-6 py-4">
158149
<button className="relative z-[2] flex items-center gap-0.5 text-neu-800">
159150
<PlusIcon className="size-4 text-sec-dark" />
160151
<span className="typography-body-xs">Add to calendar</span>
161152
</button>
162153
</div>
163-
</div>
154+
</footer>
164155
)}
165156
</div>
166157
)

src/app/conf/_design-system/session-card.example.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)