From d090d7c5757dd142bfbb7e08d13080345f97268b Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 22:24:40 -0400 Subject: [PATCH 01/15] ui: RSVP and link buttons above flyer, star button outside card Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 108 +++++++++++++++++++++++++---------- src/components/ListView.tsx | 55 +++++++++++------- src/components/OGImage.tsx | 6 +- 3 files changed, 117 insertions(+), 52 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index 1e4c2e9d..f46fe626 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -42,6 +42,8 @@ interface EventCardProps { onRsvp?: () => void; /** Compact mode for map popups — smaller text, no impression tracking */ compact?: boolean; + /** When true, RSVP + link buttons render above the flyer inside the card (star button is external) */ + buttonsAboveFlyer?: boolean; } function FriendsGoingModal({ @@ -144,6 +146,7 @@ export const EventCard = memo(function EventCard({ rsvpStatus, onRsvp, compact, + buttonsAboveFlyer, }: EventCardProps) { const [showFriendsModal, setShowFriendsModal] = useState(false); const [showCheckedInModal, setShowCheckedInModal] = useState(false); @@ -230,37 +233,66 @@ export const EventCard = memo(function EventCard({ style={event.isFeatured ? { borderColor: 'var(--theme-popup-featured-border)' } : hasFriends ? { borderLeftColor: 'var(--friend-blue)' } : undefined} > {/* Left column: action buttons + cover image */} -
-
- {onItineraryToggle && ( - - )} - {onRsvp && event.link && ( -
- -
- )} - {event.link && ( - - )} + {event.link && ( + + )} +
+ ) : null} + {/* Flyer image below buttons */} + {event.link && }
- {event.link && } - + ) : ( +
+
+ {onItineraryToggle && ( + + )} + {onRsvp && event.link && ( +
+ +
+ )} + {event.link && ( + + )} +
+ {event.link && } +
+ )} {/* Right: event details */}
@@ -446,3 +478,21 @@ export const EventCard = memo(function EventCard({
); }); + +/* ------------------------------------------------------------------ */ +/* EventCardActions — external star button for buttonsAboveFlyer mode */ +/* ------------------------------------------------------------------ */ + +export function EventCardActions({ event, isInItinerary, onItineraryToggle }: { + event: ETHDenverEvent; + isInItinerary: boolean; + onItineraryToggle: (eventId: string) => void; +}) { + return ( + + ); +} diff --git a/src/components/ListView.tsx b/src/components/ListView.tsx index 8bcbcac0..4fdf6707 100644 --- a/src/components/ListView.tsx +++ b/src/components/ListView.tsx @@ -5,7 +5,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import type { ETHDenverEvent, ReactionEmoji, NativeAd, FriendInfo } from '@/lib/types'; import { formatDateLabel } from '@/lib/utils'; import { sortByStartTime } from '@/lib/time-parse'; -import { EventCard } from './EventCard'; +import { EventCard, EventCardActions } from './EventCard'; import { FeaturedSection } from './FeaturedSection'; import NativeAdCard from './NativeAdCard'; import { imageCache, FlyerLightbox } from './OGImage'; @@ -446,26 +446,39 @@ export const ListView = memo(function ListView({ ) : (
- setLightboxEventIndex(virtualRow.index)} - rsvpStatus={getRsvpStatus?.(item.event.id)} - onRsvp={item.event.link ? () => onRsvp?.(item.event.id, item.event.link!, item.event.name) : undefined} - /> +
+ {onItineraryToggle && ( +
+ +
+ )} +
+ setLightboxEventIndex(virtualRow.index)} + rsvpStatus={getRsvpStatus?.(item.event.id)} + onRsvp={item.event.link ? () => onRsvp?.(item.event.id, item.event.link!, item.event.name) : undefined} + buttonsAboveFlyer + /> +
+
)} diff --git a/src/components/OGImage.tsx b/src/components/OGImage.tsx index 1512f54a..41c2d22c 100644 --- a/src/components/OGImage.tsx +++ b/src/components/OGImage.tsx @@ -16,11 +16,13 @@ interface OGImageProps { isInItinerary?: boolean; onItineraryToggle?: (eventId: string) => void; friendsGoing?: FriendInfo[]; + /** Optional className to override the default width classes on the thumbnail container */ + className?: string; } export const imageCache = new Map(); -export function OGImage({ url, eventId, rsvpUrl, onOpenLightbox, isInItinerary, onItineraryToggle, friendsGoing }: OGImageProps) { +export function OGImage({ url, eventId, rsvpUrl, onOpenLightbox, isInItinerary, onItineraryToggle, friendsGoing, className }: OGImageProps) { const [imageUrl, setImageUrl] = useState( imageCache.get(url) ?? null ); @@ -86,7 +88,7 @@ export function OGImage({ url, eventId, rsvpUrl, onOpenLightbox, isInItinerary, <>
{ e.stopPropagation(); if (!imageUrl) return; From 43651fdc6ecc90ee0f49dc4495bfca3eecce05bf Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 22:29:19 -0400 Subject: [PATCH 02/15] ui: all action buttons above flyer thumbnail Move star, RSVP, and link buttons into a row above the flyer inside the card instead of having star outside. Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 50 ++++++++++++++++++-------------- src/components/ListView.tsx | 56 ++++++++++++++---------------------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index f46fe626..d3910902 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -235,28 +235,34 @@ export const EventCard = memo(function EventCard({ {/* Left column: action buttons + cover image */} {buttonsAboveFlyer ? (
- {/* Small row of RSVP + link buttons above flyer */} - {(onRsvp && event.link) || event.link ? ( -
- {onRsvp && event.link && ( - - )} - {event.link && ( - - )} -
- ) : null} + {/* Row of all action buttons above flyer */} +
+ {onItineraryToggle && ( + + )} + {onRsvp && event.link && ( + + )} + {event.link && ( + + )} +
{/* Flyer image below buttons */} {event.link && }
diff --git a/src/components/ListView.tsx b/src/components/ListView.tsx index 4fdf6707..265ff08c 100644 --- a/src/components/ListView.tsx +++ b/src/components/ListView.tsx @@ -5,7 +5,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import type { ETHDenverEvent, ReactionEmoji, NativeAd, FriendInfo } from '@/lib/types'; import { formatDateLabel } from '@/lib/utils'; import { sortByStartTime } from '@/lib/time-parse'; -import { EventCard, EventCardActions } from './EventCard'; +import { EventCard } from './EventCard'; import { FeaturedSection } from './FeaturedSection'; import NativeAdCard from './NativeAdCard'; import { imageCache, FlyerLightbox } from './OGImage'; @@ -446,39 +446,27 @@ export const ListView = memo(function ListView({
) : (
-
- {onItineraryToggle && ( -
- -
- )} -
- setLightboxEventIndex(virtualRow.index)} - rsvpStatus={getRsvpStatus?.(item.event.id)} - onRsvp={item.event.link ? () => onRsvp?.(item.event.id, item.event.link!, item.event.name) : undefined} - buttonsAboveFlyer - /> -
-
+ setLightboxEventIndex(virtualRow.index)} + rsvpStatus={getRsvpStatus?.(item.event.id)} + onRsvp={item.event.link ? () => onRsvp?.(item.event.id, item.event.link!, item.event.name) : undefined} + buttonsAboveFlyer + />
)} From eac33216d34edb346207dbd499518a6d9f6e7cc4 Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 23:19:36 -0400 Subject: [PATCH 03/15] ui: move action buttons below flyer thumbnail Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index d3910902..f6b7357a 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -235,7 +235,9 @@ export const EventCard = memo(function EventCard({ {/* Left column: action buttons + cover image */} {buttonsAboveFlyer ? (
- {/* Row of all action buttons above flyer */} + {/* Flyer image */} + {event.link && } + {/* Row of all action buttons below flyer */}
{onItineraryToggle && ( )}
- {/* Flyer image below buttons */} - {event.link && }
) : (
From 3f3ad26120443c729a198bb2636b13fa4359cd27 Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 23:31:52 -0400 Subject: [PATCH 04/15] ui: hide date on mobile list cards (shown in sticky header) Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index f6b7357a..62a9c2b8 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -362,7 +362,7 @@ export const EventCard = memo(function EventCard({ }`} title={liveUrgency === 'red' ? 'Ending soon' : liveUrgency === 'yellow' ? 'Less than 1hr left' : 'Live now'} /> )} - {event.date} · {timeDisplay} + {event.date} · {timeDisplay}

{(checkInCount ?? 0) > 0 && ( From b15b99b5821092853403dea7c56f5e2868039d1f Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 23:32:57 -0400 Subject: [PATCH 05/15] ui: style mobile list time like table view (stacked start/end) Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index 62a9c2b8..f936d484 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -362,7 +362,11 @@ export const EventCard = memo(function EventCard({ }`} title={liveUrgency === 'red' ? 'Ending soon' : liveUrgency === 'yellow' ? 'Less than 1hr left' : 'Live now'} /> )} - {event.date} · {timeDisplay} + {event.date} · {timeDisplay} + + {event.isAllDay ? 'All Day' : event.startTime} + {!event.isAllDay && event.endTime && {event.endTime}} +

{(checkInCount ?? 0) > 0 && ( From 744d7246b13e6ef7fd105210ace5f0e8c7859857 Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 23:41:14 -0400 Subject: [PATCH 06/15] ui: mobile list cards show location inline with time, hide date icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On mobile, time and address appear on one line (e.g. "2:00pm-4:00pm · Venue Name"). Calendar icon and separate address row hidden on mobile. Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index f936d484..c90c82d4 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -361,11 +361,11 @@ export const EventCard = memo(function EventCard({ 'bg-green-400' }`} title={liveUrgency === 'red' ? 'Ending soon' : liveUrgency === 'yellow' ? 'Less than 1hr left' : 'Live now'} /> )} - + {event.date} · {timeDisplay} - - {event.isAllDay ? 'All Day' : event.startTime} - {!event.isAllDay && event.endTime && {event.endTime}} + + {event.isAllDay ? 'All Day' : event.startTime}{!event.isAllDay && event.endTime ? `-${event.endTime}` : ''} + {event.address && · {shortenAddress(event.address)}}

{(checkInCount ?? 0) > 0 && ( @@ -393,11 +393,11 @@ export const EventCard = memo(function EventCard({ )}
- {/* Address */} + {/* Address — hidden on mobile (shown inline with time) */} {event.address && ( + className="hidden sm:flex w-full text-[var(--theme-text-muted)] hover:text-[var(--theme-text-secondary)] text-sm mt-1 items-start gap-1 overflow-hidden transition-colors min-w-0"> {shortenAddress(event.address)} {userLocation && event.lat && event.lng && ( From 698a93e4869925001c234c2820514ec6ee8f7196 Mon Sep 17 00:00:00 2001 From: snackman Date: Tue, 5 May 2026 23:46:10 -0400 Subject: [PATCH 07/15] ui: match table view time style on mobile list cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start time + location on first line, end time below in smaller muted text — same stacked pattern as mobile table view. Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index c90c82d4..3afff04b 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -363,9 +363,12 @@ export const EventCard = memo(function EventCard({ )} {event.date} · {timeDisplay} - - {event.isAllDay ? 'All Day' : event.startTime}{!event.isAllDay && event.endTime ? `-${event.endTime}` : ''} - {event.address && · {shortenAddress(event.address)}} + + + {event.isAllDay ? 'All Day' : event.startTime} + {event.address && · {shortenAddress(event.address)}} + + {!event.isAllDay && event.endTime && {event.endTime}}

{(checkInCount ?? 0) > 0 && ( From 550f2b0932c577cb5e6995990ad418e650107511 Mon Sep 17 00:00:00 2001 From: snackman Date: Wed, 6 May 2026 00:03:11 -0400 Subject: [PATCH 08/15] ui: move all buttons to bottom strip on list cards Action buttons (+, RSVP, link) and reactions/friends now live in a single strip along the bottom of each card with a subtle top border. Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 79 +++++++++++++++++------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index 3afff04b..83621ee4 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -234,37 +234,9 @@ export const EventCard = memo(function EventCard({ > {/* Left column: action buttons + cover image */} {buttonsAboveFlyer ? ( -
- {/* Flyer image */} +
+ {/* Flyer image only */} {event.link && } - {/* Row of all action buttons below flyer */} -
- {onItineraryToggle && ( - - )} - {onRsvp && event.link && ( - - )} - {event.link && ( - - )} -
) : (
@@ -426,8 +398,41 @@ export const EventCard = memo(function EventCard({

{event.note}

)} - {/* Bottom row: friends + reactions + checked-in indicator */} -
+ {/* Bottom strip: action buttons + reactions */} +
+ {buttonsAboveFlyer && onItineraryToggle && ( + + )} + {buttonsAboveFlyer && onRsvp && event.link && ( + + )} + {buttonsAboveFlyer && event.link && ( + + )} + {onToggleReaction && ( + + )} {friendsGoing && friendsGoing.length > 0 && ( )} - {onToggleReaction && ( - - )} - {/* Comments disabled until social verification is in place */} - {/* */} {checkedInFriends && checkedInFriends.length > 0 && ( {showPicker && ( From 746d9d1936d1f837eafa5e2d284336f71266dd0d Mon Sep 17 00:00:00 2001 From: snackman Date: Wed, 6 May 2026 00:54:30 -0400 Subject: [PATCH 11/15] ui: propagate bottom-strip buttons to map/table popups, fix lightbox star color - Map popup cards and table modal now use buttonsAboveFlyer layout - Lightbox StarButton forced white for light mode visibility Co-Authored-By: Claude Opus 4.6 --- src/components/EventPopup.tsx | 2 ++ src/components/OGImage.tsx | 12 +++++++----- src/components/TableView.tsx | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/EventPopup.tsx b/src/components/EventPopup.tsx index 43fa3ec3..cf906acd 100644 --- a/src/components/EventPopup.tsx +++ b/src/components/EventPopup.tsx @@ -95,6 +95,7 @@ export const EventPopup = memo(function EventPopup({ rsvpStatus={rsvpStatus} onRsvp={onRsvp} compact + buttonsAboveFlyer />
@@ -155,6 +156,7 @@ export function MultiEventPopup({ onToggleReaction={onToggleReaction} commentCount={commentCounts?.get(event.id)} compact + buttonsAboveFlyer />
))} diff --git a/src/components/OGImage.tsx b/src/components/OGImage.tsx index 31401204..6a160418 100644 --- a/src/components/OGImage.tsx +++ b/src/components/OGImage.tsx @@ -255,11 +255,13 @@ export function FlyerLightbox({ imageUrl, rsvpUrl, onClose, onPrev, onNext, even />
{eventId && onItineraryToggle && ( - +
+ +
)} {rsvpUrl && ( onOpenLightbox() : undefined} + buttonsAboveFlyer />
From 02142b99b9bf4afa6aa77ad47b142951e15285da Mon Sep 17 00:00:00 2001 From: snackman Date: Wed, 6 May 2026 00:56:38 -0400 Subject: [PATCH 12/15] ui: standardize lightbox flyer size with fixed height All flyers now display at a consistent 60vh height in the lightbox, preventing layout shifts when navigating between images. Co-Authored-By: Claude Opus 4.6 --- src/components/OGImage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OGImage.tsx b/src/components/OGImage.tsx index 6a160418..f81ee96f 100644 --- a/src/components/OGImage.tsx +++ b/src/components/OGImage.tsx @@ -133,7 +133,7 @@ export function OGImage({ url, eventId, rsvpUrl, onOpenLightbox, isInItinerary,
{eventId && onItineraryToggle && ( From fe2723ccf74a958989c4000fceb6068b4b2e7d8e Mon Sep 17 00:00:00 2001 From: snackman Date: Wed, 6 May 2026 01:15:22 -0400 Subject: [PATCH 13/15] ui: tighten card padding and gaps for higher density MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Outer padding: 16px → 12px - Image-to-details gap: 16px → 10px - Tag row margin: 12px → 8px - Bottom strip margin: 8px → 6px - List item spacing: 12px → 8px ~25-30% more events visible on screen. Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 6 +++--- src/components/ListView.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index 83621ee4..c97ee165 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -225,7 +225,7 @@ export const EventCard = memo(function EventCard({ : `${event.startTime}${event.endTime ? ` - ${event.endTime}` : ''}`; return ( -
+
{event.tags.map((tag) => ( ))} @@ -399,7 +399,7 @@ export const EventCard = memo(function EventCard({ )} {/* Bottom strip: action buttons + reactions */} -
+
{buttonsAboveFlyer && onItineraryToggle && (
) : item.kind === 'ad' ? ( -
+
) : ( -
+
Date: Wed, 6 May 2026 01:16:28 -0400 Subject: [PATCH 14/15] ui: remove separator line from bottom action strip Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index c97ee165..e90c1cbb 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -399,7 +399,7 @@ export const EventCard = memo(function EventCard({ )} {/* Bottom strip: action buttons + reactions */} -
+
{buttonsAboveFlyer && onItineraryToggle && ( Date: Wed, 6 May 2026 01:20:23 -0400 Subject: [PATCH 15/15] fix: prevent location from wrapping between start and end time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Time and address now on one line: "11:00a-2:00p · Venue Name" with truncation on long addresses. Co-Authored-By: Claude Opus 4.6 --- src/components/EventCard.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index e90c1cbb..f4738b7c 100644 --- a/src/components/EventCard.tsx +++ b/src/components/EventCard.tsx @@ -335,12 +335,9 @@ export const EventCard = memo(function EventCard({ )} {event.date} · {timeDisplay} - - - {event.isAllDay ? 'All Day' : event.startTime} - {event.address && · {shortenAddress(event.address)}} - - {!event.isAllDay && event.endTime && {event.endTime}} + + {event.isAllDay ? 'All Day' : `${event.startTime}${event.endTime ? `-${event.endTime}` : ''}`} + {event.address && · {shortenAddress(event.address)}}

{(checkInCount ?? 0) > 0 && (