diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx index 1e4c2e9..d592842 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, action buttons are rendered externally (ListView desktop) — hides internal button column and enlarges flyer */ + externalActions?: boolean; } function FriendsGoingModal({ @@ -144,6 +146,7 @@ export const EventCard = memo(function EventCard({ rsvpStatus, onRsvp, compact, + externalActions, }: EventCardProps) { const [showFriendsModal, setShowFriendsModal] = useState(false); const [showCheckedInModal, setShowCheckedInModal] = useState(false); @@ -231,7 +234,7 @@ export const EventCard = memo(function EventCard({ > {/* Left column: action buttons + cover image */}
-
+
{onItineraryToggle && ( )}
- {event.link && } + {event.link && }
{/* Right: event details */} @@ -446,3 +449,66 @@ export const EventCard = memo(function EventCard({
); }); + +/* ------------------------------------------------------------------ */ +/* Standalone action buttons — rendered outside the card in ListView */ +/* ------------------------------------------------------------------ */ + +interface EventCardActionsProps { + event: ETHDenverEvent; + isInItinerary: boolean; + onItineraryToggle?: (eventId: string) => void; + rsvpStatus?: 'idle' | 'confirmed'; + onRsvp?: () => void; +} + +export function EventCardActions({ + event, + isInItinerary, + onItineraryToggle, + rsvpStatus, + onRsvp, +}: EventCardActionsProps) { + const [copied, setCopied] = useState(false); + + const handleCopyLink = (e: React.MouseEvent) => { + e.stopPropagation(); + if (!event.link) return; + navigator.clipboard.writeText(event.link).then(() => { + trackCopyEventLink(event.name); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + return ( +
+ {onItineraryToggle && ( + + )} + {onRsvp && event.link && ( +
+ +
+ )} + {event.link && ( + + )} +
+ ); +} diff --git a/src/components/ListView.tsx b/src/components/ListView.tsx index 8bcbcac..8026add 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,40 @@ 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} - /> +
+
+ onRsvp?.(item.event.id, item.event.link!, item.event.name) : undefined} + /> +
+
+ setLightboxEventIndex(virtualRow.index)} + rsvpStatus={getRsvpStatus?.(item.event.id)} + onRsvp={item.event.link ? () => onRsvp?.(item.event.id, item.event.link!, item.event.name) : undefined} + externalActions + /> +
+
)} diff --git a/src/components/OGImage.tsx b/src/components/OGImage.tsx index 1512f54..ebbc6f5 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[]; + /** Override the default width classes (e.g. for enlarged flyer when buttons are external) */ + 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;