From 1c772cd31fd64fa2fd0ba164a3638d323de48753 Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Mon, 25 May 2026 19:09:49 +0530 Subject: [PATCH] fix meetings ui --- apps/x/apps/renderer/src/App.css | 9 - .../renderer/src/components/meetings-view.tsx | 241 ++++++++---------- 2 files changed, 109 insertions(+), 141 deletions(-) diff --git a/apps/x/apps/renderer/src/App.css b/apps/x/apps/renderer/src/App.css index 28f9539c..64bd9779 100644 --- a/apps/x/apps/renderer/src/App.css +++ b/apps/x/apps/renderer/src/App.css @@ -303,15 +303,6 @@ box-shadow: none; } -.upcoming-event-row { - background-color: transparent; - transition: background-color 120ms ease; -} - -.upcoming-event-row:hover { - background-color: var(--gm-bg-pill-hover); -} - .gmail-row-selected { background: var(--gm-bg-row-selected); box-shadow: inset 2px 0 0 var(--gm-accent); diff --git a/apps/x/apps/renderer/src/components/meetings-view.tsx b/apps/x/apps/renderer/src/components/meetings-view.tsx index b4e369e7..58700b43 100644 --- a/apps/x/apps/renderer/src/components/meetings-view.tsx +++ b/apps/x/apps/renderer/src/components/meetings-view.tsx @@ -212,6 +212,42 @@ function formatEventTimeRange(event: UpcomingEvent): string { return `${start} – ${end}` } +// Compact range for the upcoming list: drops the leading meridiem when both +// ends share it ("9:00 – 11:00 AM" instead of "9:00 AM – 11:00 AM"). +function formatEventTimeRangeCompact(event: UpcomingEvent): string { + if (event.isAllDay) return 'All day' + const startStr = event.start.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) + if (!event.end) return startStr + const sameDay = localDateKey(event.start) === localDateKey(event.end) + if (!sameDay) return formatEventTimeRange(event) + const endStr = event.end.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) + const meridiemRe = /\s*[AP]M$/i + const startMer = startStr.match(meridiemRe)?.[0]?.trim().toUpperCase() + const endMer = endStr.match(meridiemRe)?.[0]?.trim().toUpperCase() + if (startMer && endMer && startMer === endMer) { + return `${startStr.replace(meridiemRe, '')} – ${endStr}` + } + return `${startStr} – ${endStr}` +} + +// Whether a timed event is happening right now. +function isEventNow(event: UpcomingEvent): boolean { + if (event.isAllDay) return false + const now = Date.now() + const start = event.start.getTime() + const end = event.end ? event.end.getTime() : start + 30 * 60 * 1000 + return start <= now && now < end +} + +// Human label for the conferencing provider behind an event's join link. +function meetingPlatformLabel(link: string | null): string | null { + if (!link) return null + if (/zoom\.us|zoomgov\.com/i.test(link)) return 'Zoom' + if (/teams\.(?:microsoft|live)\.com/i.test(link)) return 'Teams' + if (/meet\.google\.com/i.test(link)) return 'Meet' + return 'Video call' +} + function formatEventDetailTime(event: UpcomingEvent): string { if (!event.isAllDay) { const date = event.start.toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric' }) @@ -431,8 +467,9 @@ function UpcomingEvents() { break } }) - // Refresh on the hour so day labels and "ended" filtering stay current. - const tick = setInterval(() => setRefreshTick((t) => t + 1), 60 * 60 * 1000) + // Refresh every minute so the "now" highlight, day labels, and "ended" + // filtering stay current without waiting on a calendar sync. + const tick = setInterval(() => setRefreshTick((t) => t + 1), 60 * 1000) return () => { cleanup() clearInterval(tick) @@ -462,10 +499,7 @@ function UpcomingEvents() { Coming up {loading && events.length === 0 ? null : ( - + {totalVisible} {totalVisible === 1 ? 'event' : 'events'} )} @@ -491,16 +525,12 @@ function UpcomingEvents() { ) : error ? (
{error}
) : ( -
- {visibleDays.map((day, idx) => ( - + {visibleDays.map((day) => ( + ))}
@@ -511,63 +541,57 @@ function UpcomingEvents() { ) } -function UpcomingDayRow({ day, isToday, isLast }: { day: DayGroup; isToday: boolean; isLast: boolean }) { +function UpcomingDayCard({ day, isToday }: { day: DayGroup; isToday: boolean }) { const dayNum = day.date.getDate() const month = day.date.toLocaleDateString([], { month: 'short' }) const weekday = day.date.toLocaleDateString([], { weekday: 'short' }) + const count = day.events.length return ( -
-
- - {dayNum} - - - - {month} - {isToday ? ( - - ) : null} +
+
+
+ {dayNum} + + {month} · {weekday} - {weekday} + {isToday ? ( + + Today + + ) : null} +
+ + {count} {count === 1 ? 'event' : 'events'}
-
- {day.events.length === 0 ? ( -
- - {isToday ? 'No events today' : 'No events'} -
- ) : ( - day.events.map((ev) => ) - )} -
+ + {count === 0 ? ( +
+ {isToday ? 'No events today' : 'No events'} +
+ ) : ( + day.events.map((ev, idx) => ( + + )) + )}
) } -function UpcomingEventItem({ event }: { event: UpcomingEvent }) { - const [open, setOpen] = useState(false) +function NowBadge() { + return ( + + Now + + ) +} +function UpcomingEventItem({ event, isLast }: { event: UpcomingEvent; isLast: boolean }) { + const [open, setOpen] = useState(false) + const isNow = isEventNow(event) + const platform = meetingPlatformLabel(event.conferenceLink) + const subtitle = platform ?? event.location const titleAndLocation = event.location ? `${event.summary} · ${event.location}` : event.summary return ( @@ -578,31 +602,29 @@ function UpcomingEventItem({ event }: { event: UpcomingEvent }) { tabIndex={0} title={titleAndLocation} className={cn( - 'upcoming-event-row group flex w-full items-center gap-3 px-3 py-2 text-left cursor-pointer', + 'group flex w-full cursor-pointer items-center gap-4 px-5 py-3 text-left transition-colors', + !isLast && 'border-b', + isNow ? 'bg-background' : 'hover:bg-background', )} - style={{ color: 'var(--gm-text)', minHeight: 40 }} > - - - - {event.summary} - - - {formatEventTimeRange(event)} - {event.location ? · {event.location} : null} - + + {formatEventTimeRangeCompact(event)} -
+ + + + {event.summary} + + {isNow ? : null} + + {subtitle ? ( + + {platform ? + ) : null} + +
{event.conferenceLink ? ( triggerMeetingCapture(event, true)} @@ -613,16 +635,9 @@ function UpcomingEventItem({ event }: { event: UpcomingEvent }) { type="button" onClick={(e) => { e.stopPropagation(); triggerMeetingCapture(event, false) }} onMouseDown={(e) => e.stopPropagation()} - className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs transition-colors" - style={{ - background: 'var(--gm-bg-pill)', - color: 'var(--gm-text)', - border: '1px solid var(--gm-border)', - }} - onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.background = 'var(--gm-bg-pill-hover)' }} - onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.background = 'var(--gm-bg-pill)' }} + className="inline-flex items-center gap-1.5 rounded-md border bg-background px-2.5 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent" > - + Take notes )} @@ -786,26 +801,14 @@ function SplitJoinButton({ onJoinAndNotes, onNotesOnly }: { }, [open]) return ( -
+
{open && ( -
+