Setting the file. One moment.
Events View · Wix Headless Templates · wix/skills · Skills Docs
ContentsBack to the top of the page Bundled file Event Registration View
events/app/components/events/ EventsView.tsx
TypeScript · 134 lines · 5 KB
9 href : string ;
10 className ?: string ;
11 children ?: ReactNode ;
12 }
13
14 const PlainLink = ({ href , className , children } : LinkLikeProps ) => (
15 < a href ={ href } className ={ className } >
16 { children }
17 </ a >
18 );
19
20 export interface EventCardProps {
21 event : EventSummary ;
22 eventHref ?: ( slug : string ) => string ;
23 LinkComponent ?: ComponentType < LinkLikeProps >;
24 }
25
26 export function EventCard ({
27 event ,
28 eventHref = ( slug ) => `/events/${ encodeURIComponent ( slug ) }` ,
29 LinkComponent = PlainLink,
30 } : EventCardProps ) {
31 return (
32 < LinkComponent href ={ eventHref (event.slug) } className = "group block no-underline" >
33 < div className = "relative aspect-[3/2] overflow-hidden rounded-lg bg-secondary" >
34 { event.imageUrl && (
35 < img
36 {... imgAttrs (event.imageUrl, "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw" , 2 / 3 ) }
37 alt ={ event.title }
38 className = "h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
39 />
40 ) }
41 { event.soldOut && (
42 < span className = "absolute left-3 top-3 rounded-control bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur" >
43 Sold out
44 </ span >
45 ) }
46 </ div >
47 { event.dateLabel && < p className = "eyebrow mt-3" > { event.dateLabel } </ p > }
48 < p className = "mt-1 text-sm font-medium text-foreground" > { event.title } </ p >
49 < p className = "mt-0.5 text-xs text-muted-foreground" >
50 { event.locationType === "ONLINE" ? "Online" : event.locationName }
51 { event.priceLabel ? ` · ${ event . priceLabel }` : "" }
52 </ p >
53 </ LinkComponent >
54 );
55 }
56
57 export interface EventsViewProps {
58 initialEvents ?: EventSummary [];
59 /** From fetchEventsPage — lets the list know there is more to load. */
60 initialTotal ?: number ;
61 emptyMessage ?: string ;
62 eventHref ?: EventCardProps [ "eventHref" ];
63 LinkComponent ?: ComponentType < LinkLikeProps >;
64 CardComponent ?: ComponentType < EventCardProps >;
65 }
66
67 const pill = ( active : boolean ) =>
68 `rounded-control border px-4 py-1.5 text-sm font-medium transition-colors ${
69 active
70 ? "border-primary bg-primary text-primary-foreground"
71 : "border-border text-foreground hover:bg-secondary"
72 }` ;
73
74 export default function EventsView ({
75 initialEvents,
76 initialTotal,
77 emptyMessage = "No upcoming events — check back soon." ,
78 eventHref,
79 LinkComponent,
80 CardComponent = EventCard ,
81 } : EventsViewProps) {
82 const { events , categories , activeCategoryId , setActiveCategoryId , hasMore , loadingMore , loadMore , error } = useEvents ({
83 initialEvents,
84 initialTotal,
85 });
86
87 return (
88 < div >
89 { categories. length > 1 && (
90 < div className = "mb-8 flex flex-wrap gap-2" role = "group" aria-label = "Categories" >
91 < button type = "button" className ={ pill (activeCategoryId === null ) } onClick ={ () => setActiveCategoryId ( null ) } >
92 All
93 </ button >
94 { categories. map (( c ) => (
95 < button key ={ c.id } type = "button" className ={ pill (activeCategoryId === c.id) } onClick ={ () => setActiveCategoryId (c.id) } >
96 { c.name }
97 </ button >
98 )) }
99 </ div >
100 ) }
101 { error && < p className = "mb-4 text-sm text-destructive" > { error } </ p > }
102 { events === null ? (
103 < div className = "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3" aria-busy = "true" >
104 { Array. from ({ length: 6 }, ( _ , i ) => (
105 < div key ={ i } >
106 < div className = "aspect-[3/2] animate-pulse rounded-lg bg-secondary" />
107 < div className = "mt-3 h-3.5 w-2/3 animate-pulse rounded bg-secondary" />
108 </ div >
109 )) }
110 </ div >
111 ) : events. length === 0 ? (
112 < p className = "py-16 text-center text-muted-foreground" > { loadingMore ? "Loading…" : emptyMessage } </ p >
113 ) : (
114 < div className = "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3" >
115 { events. map (( e ) => (
116 < CardComponent key ={ e.id } event ={ e } eventHref ={ eventHref } LinkComponent ={ LinkComponent } />
117 )) }
118 </ div >
119 ) }
120 { hasMore && (
121 < div className = "mt-10 text-center" >
122 < button
123 type = "button"
124 disabled ={ loadingMore }
125 onClick ={ () => void loadMore () }
126 className = "rounded-control border border-border px-6 py-2.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary disabled:opacity-50"
127 >
128 { loadingMore ? "Loading…" : "Load more" }
129 </ button >
130 </ div >
131 ) }
132 </ div >
133 );
134 }