fix filtering in events page (#40697)

* fix filtering in events page

* make agency on-demand
This commit is contained in:
Alan Daniel authored and GitHub committed 2025-11-24 15:01:24 -04:00
1 parent b709428112
commit 7747ef0b29
7 files changed
+129 -17

No files matched your search

@@ -8,7 +8,7 @@ meta_description: >-
Learn how leading agencies use Postgres expertise and strategic services to
command premium pricing while competitors race to the bottom on AI-built MVPs.
type: webinar
onDemand: false
onDemand: true
date: '2025-11-20T07:00:00.000-08:00'
timezone: America/Los_Angeles
duration: 45 mins
+48 -4
View File
@@ -2,13 +2,14 @@
import { createContext, useContext, useState, useEffect, useMemo, ReactNode } from 'react'
import { SupabaseEvent, SUPABASE_HOST } from '~/lib/eventsTypes'
import { cover } from 'three/src/extras/TextureUtils.js'
interface EventsContextValue {
// Events data
allEvents: SupabaseEvent[]
filteredEvents: SupabaseEvent[]
filteredOnDemandEvents: SupabaseEvent[]
staticEvents: SupabaseEvent[]
onDemandEvents: SupabaseEvent[]
lumaEvents: SupabaseEvent[]
featuredEvent: SupabaseEvent | undefined
@@ -30,9 +31,10 @@ const EventsContext = createContext<EventsContextValue | undefined>(undefined)
interface EventsProviderProps {
children: ReactNode
staticEvents: SupabaseEvent[]
onDemandEvents: SupabaseEvent[]
}
export function EventsProvider({ children, staticEvents }: EventsProviderProps) {
export function EventsProvider({ children, staticEvents, onDemandEvents }: EventsProviderProps) {
const [lumaEvents, setLumaEvents] = useState<SupabaseEvent[]>([])
const [isLoading, setIsLoading] = useState(true)
const [searchQuery, setSearchQuery] = useState('')
@@ -99,9 +101,12 @@ export function EventsProvider({ children, staticEvents }: EventsProviderProps)
}, [staticEvents, lumaEvents])
// Calculate categories with counts
// - Webinar: count only upcoming webinars (not on-demand)
// - On-demand: count only on-demand events
const categories = useMemo(() => {
const categoryCounts: { [key: string]: number } = { all: 0 }
// Count upcoming events (excluding on-demand)
allEvents.forEach((event) => {
categoryCounts.all += 1
@@ -110,8 +115,15 @@ export function EventsProvider({ children, staticEvents }: EventsProviderProps)
})
})
// Count on-demand events separately
onDemandEvents.forEach((event) => {
categoryCounts.all += 1
// Add to 'on-demand' category instead of 'webinar'
categoryCounts['on-demand'] = (categoryCounts['on-demand'] || 0) + 1
})
return categoryCounts
}, [allEvents])
}, [allEvents, onDemandEvents])
// Toggle category selection
const toggleCategory = (category: string) => {
@@ -135,8 +147,13 @@ export function EventsProvider({ children, staticEvents }: EventsProviderProps)
})
}
// Filter events by search query and category
// Filter upcoming events by search query and category
const filteredEvents = useMemo(() => {
// If 'on-demand' is selected, don't show upcoming events
if (selectedCategories.includes('on-demand') && !selectedCategories.includes('all')) {
return []
}
let filtered = allEvents
// Filter by categories (multiple selection)
@@ -167,6 +184,31 @@ export function EventsProvider({ children, staticEvents }: EventsProviderProps)
})
}, [allEvents, selectedCategories, searchQuery])
// Filter on-demand events separately by search query and category
const filteredOnDemandEvents = useMemo(() => {
// If specific categories are selected (not 'all' and not 'on-demand'), don't show on-demand events
if (!selectedCategories.includes('all') && !selectedCategories.includes('on-demand')) {
return []
}
let filtered = onDemandEvents
// Filter by search query
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase()
filtered = filtered.filter((event) => {
const titleMatch = event.title?.toLowerCase().includes(query)
const descriptionMatch = event.description?.toLowerCase().includes(query)
const locationMatch = event.location?.toLowerCase().includes(query)
const tagsMatch = event.tags?.some((tag) => tag.toLowerCase().includes(query))
return titleMatch || descriptionMatch || locationMatch || tagsMatch
})
}
return filtered
}, [onDemandEvents, selectedCategories, searchQuery])
// Featured event: nearest upcoming event, or if none, the most recent past event
const featuredEvent = useMemo(() => {
if (allEvents.length === 0) return undefined
@@ -208,7 +250,9 @@ export function EventsProvider({ children, staticEvents }: EventsProviderProps)
const value: EventsContextValue = {
allEvents,
filteredEvents,
filteredOnDemandEvents,
staticEvents,
onDemandEvents,
lumaEvents,
isLoading,
searchQuery,
+2 -2
View File
@@ -10,7 +10,7 @@ export const metadata: Metadata = {
export default async function EventsPage() {
// This needs to be server-side as we use FS api.
const staticEvents = await getStaticEvents()
const { upcomingEvents, onDemandEvents } = await getStaticEvents()
return <EventClientRenderer staticEvents={staticEvents.upcomingEvents} />
return <EventClientRenderer staticEvents={upcomingEvents} onDemandEvents={onDemandEvents} />
}
+11 -3
View File
@@ -3,7 +3,7 @@
import { CalendarIcon, MapPinIcon } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { cn, Button } from 'ui'
import { cn, Button, Badge } from 'ui'
import { useEvents } from '~/app/events/context'
import { formatHosts } from '~/lib/eventsUtils'
@@ -17,7 +17,9 @@ export function EventBanner() {
if (!featuredEvent) return null
return (
<section className={cn('grid md:grid-cols-[minmax(320px,35%),1fr] gap-6 lg:gap-12')}>
<section
className={cn('grid md:grid-cols-[minmax(320px,35%),1fr] items-start gap-6 lg:gap-12')}
>
<CoverImage url={featuredEvent.cover_url} />
<article className="flex flex-col md:justify-center gap-6 lg:py-2">
@@ -140,14 +142,20 @@ const LocationWidget = ({ location }: { location?: string }) => {
const CoverImage = ({ url }: { url?: string }) => {
if (!url)
return (
<div className="w-full bg-surface-100 aspect-square border rounded-lg hidden md:grid place-items-center">
<div className="w-full bg-surface-100 aspect-square border rounded-lg hidden md:grid place-items-center relative">
<Logo />
<Badge variant="brand" className="absolute bottom-4 right-4">
Upcoming
</Badge>
</div>
)
return (
<div className="w-full bg-surface-100 hidden md:block aspect-square border rounded-lg overflow-hidden relative">
<img src={url} alt="Event Cover" className="object-cover object-center w-full" />
<Badge variant="brand" className="absolute bottom-4 right-4">
Upcoming
</Badge>
</div>
)
}
@@ -7,9 +7,15 @@ import { EventBanner } from '~/components/Events/new/EventBanner'
import { EventsProvider } from '~/app/events/context'
import { EventGallery } from './EventGallery'
export function EventClientRenderer({ staticEvents }: { staticEvents: SupabaseEvent[] }) {
export function EventClientRenderer({
staticEvents,
onDemandEvents,
}: {
staticEvents: SupabaseEvent[]
onDemandEvents: SupabaseEvent[]
}) {
return (
<EventsProvider staticEvents={staticEvents}>
<EventsProvider staticEvents={staticEvents} onDemandEvents={onDemandEvents}>
<DefaultLayout className="flex flex-col">
<SectionContainer className="border-x border-b !py-8">
<h1 className="h3 !p-0 !m-0">
+54 -4
View File
@@ -16,7 +16,7 @@ const CATEGORIES_FILTERS = [
]
export function EventList() {
const { isLoading, filteredEvents } = useEvents()
const { isLoading, filteredEvents, filteredOnDemandEvents } = useEvents()
const getCategoryLabel = (value: string) => {
const category = CATEGORIES_FILTERS.find((cat) => cat.value === value)
@@ -27,7 +27,7 @@ export function EventList() {
return <EventListSkeleton />
}
// Group events by date
// Group upcoming events by date
const eventsByDate = filteredEvents.reduce(
(acc, event) => {
const eventDate = new Date(event.date).toLocaleDateString('en-US', {
@@ -46,6 +46,9 @@ export function EventList() {
{} as Record<string, typeof filteredEvents>
)
const hasUpcomingEvents = Object.keys(eventsByDate).length > 0
const hasOnDemandEvents = filteredOnDemandEvents.length > 0
return (
<div className="flex flex-col gap-y-8 min-h-72">
{Object.entries(eventsByDate).map(([date, events], index) => (
@@ -99,8 +102,55 @@ export function EventList() {
</div>
))}
{/* emtpy state */}
{Object.keys(eventsByDate).length === 0 && (
{/* On-demand events section */}
{hasOnDemandEvents && (
<div className="flex flex-col gap-y-2 relative mt-8">
<div className="absolute top-2 -left-[calc(48px+11px)] rounded-full size-1.5 bg-foreground-muted" />
<h3 className="text-foreground-light font-normal">On Demand</h3>
<div className="flex flex-col gap-y-4">
{filteredOnDemandEvents.map((event, idx) => (
<div
key={`on-demand-${idx}-${event.url}`}
className="bg-surface-100 border rounded-md p-3 flex justify-between items-start relative"
>
<Link
className="inset-0 absolute"
href={event.url}
target={event.url.startsWith('http') ? '_blank' : '_self'}
title="Go to event page"
aria-hidden
/>
<div className="flex flex-col gap-2">
<h3>{event.title}</h3>
<div className="flex gap-2 items-center text-sm text-foreground-light">
<div className="size-5 rounded-full border bg-gradient-to-br from-background-surface-100 to-background-surface-200 relative">
{event.hosts[0]?.avatar_url && (
<img
src={event.hosts[0].avatar_url}
alt={event.hosts[0].name || 'Host image'}
className="absolute inset-0 w-full h-full object-cover rounded-full"
/>
)}
</div>
Hosted by {formatHosts(event.hosts).displayText}
</div>
</div>
{event.categories.map((tag, idx) => (
<Badge key={`tag-${idx}`}>{getCategoryLabel(tag)}</Badge>
))}
</div>
))}
</div>
</div>
)}
{/* empty state */}
{!hasUpcomingEvents && !hasOnDemandEvents && (
<div className="self-center text-muted my-auto flex flex-col items-center gap-y-4">
<Rows3Icon className="size-8" />
<p className="">Oops! No events found.</p>
+5 -1
View File
@@ -252,7 +252,11 @@ export const getStaticEvents = async (): Promise<{
thumb: post.thumb || '',
cover_url: (post as any).cover_url || '',
path: post.path || '',
url: post.url || '',
// For webinars, use internal path; for other events, use external link if available
url:
(post as any).type === 'webinar'
? post.url || post.path || ''
: post.link?.href || post.url || post.path || '',
tags: post.tags || [],
categories: post.categories || [],
timezone: (post as any).timezone || 'America/Los_Angeles',