1+ "use client" ;
2+ import React , { useState , useEffect } from "react" ;
3+ import NavBar from "../NavBar/page" ;
4+ import Footer from "../Footer/page" ;
5+ import { motion , AnimatePresence } from "framer-motion" ;
6+ import { sanity } from "@/lib/sanity" ; // adjust path as needed
7+
8+ // GROQ query for your event schema
9+ const query = `*[_type == "event"]{
10+ _id,
11+ title,
12+ date,
13+ location,
14+ description,
15+ images[]{
16+ asset->{
17+ _id,
18+ url
19+ }
20+ }
21+ }` ;
22+
23+ const EventCard = ( { event } : { event : any } ) => (
24+ < motion . div
25+ className = "bg-white/20 dark:bg-neutral-900/70 rounded-2xl shadow-xl overflow-hidden backdrop-blur-md border border-white/10 flex flex-col"
26+ initial = { { opacity : 0 , y : 40 , scale : 0.95 } }
27+ animate = { { opacity : 1 , y : 0 , scale : 1 } }
28+ transition = { { duration : 0.5 , type : "spring" , stiffness : 120 } }
29+ whileHover = { {
30+ scale : 1.03 ,
31+ boxShadow : "0 8px 32px 0 rgba(0,0,0,0.18)" ,
32+ } }
33+ >
34+ { event . images && event . images [ 0 ] ?. asset ?. url && (
35+ < img
36+ src = { event . images [ 0 ] . asset . url }
37+ alt = { event . title }
38+ className = "w-full h-48 object-cover"
39+ />
40+ ) }
41+ < div className = "p-6 flex flex-col flex-1" >
42+ < h3 className = "text-2xl font-bold text-gray-800 dark:text-white mb-2" >
43+ { event . title }
44+ </ h3 >
45+ < div className = "text-sm text-gray-500 dark:text-gray-300 mb-2" >
46+ < span >
47+ { event . date ? new Date ( event . date ) . toLocaleDateString ( ) : "" }
48+ </ span >
49+ · < span > { event . location } </ span >
50+ </ div >
51+ < p className = "text-gray-700 dark:text-gray-200 flex-1" >
52+ { event . description }
53+ </ p >
54+ </ div >
55+ </ motion . div >
56+ ) ;
57+
58+ const EventsPage = ( ) => {
59+ const [ search , setSearch ] = useState ( "" ) ;
60+ const [ events , setEvents ] = useState < any [ ] > ( [ ] ) ;
61+
62+ useEffect ( ( ) => {
63+ sanity . fetch ( query ) . then ( setEvents ) ;
64+ } , [ ] ) ;
65+
66+ const filteredEvents = events . filter ( ( event ) => {
67+ const searchText = search . toLowerCase ( ) ;
68+ return (
69+ event . title ?. toLowerCase ( ) . includes ( searchText ) ||
70+ event . description ?. toLowerCase ( ) . includes ( searchText ) ||
71+ event . location ?. toLowerCase ( ) . includes ( searchText )
72+ ) ;
73+ } ) ;
74+
75+ return (
76+ < div className = "flex flex-col min-h-screen bg-black" >
77+ < NavBar />
78+ < main className = "flex-1 container mx-auto px-4 py-24" >
79+ < motion . div
80+ initial = { { opacity : 0 , y : 30 } }
81+ animate = { { opacity : 1 , y : 0 } }
82+ transition = { { duration : 0.7 } }
83+ className = "text-center mb-12"
84+ >
85+ < h1 className = "text-4xl md:text-5xl font-extrabold text-white mb-3" >
86+ Upcoming < span className = "text-primary" > Events</ span >
87+ </ h1 >
88+ < p className = "text-gray-200 max-w-2xl mx-auto" >
89+ Stay updated with our latest events, workshops, and meetups.
90+ </ p >
91+ </ motion . div >
92+ < div className = "flex justify-center mb-10" >
93+ < input
94+ type = "text"
95+ placeholder = "Search events..."
96+ value = { search }
97+ onChange = { ( e ) => setSearch ( e . target . value ) }
98+ className = "w-full max-w-md px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary text-black"
99+ />
100+ </ div >
101+ < AnimatePresence >
102+ < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8" >
103+ { filteredEvents . length > 0 ? (
104+ filteredEvents . map ( ( event ) => (
105+ < EventCard key = { event . _id } event = { event } />
106+ ) )
107+ ) : (
108+ < motion . div
109+ className = "col-span-full text-center text-gray-400 py-20"
110+ initial = { { opacity : 0 } }
111+ animate = { { opacity : 1 } }
112+ >
113+ No events found.
114+ </ motion . div >
115+ ) }
116+ </ div >
117+ </ AnimatePresence >
118+ </ main >
119+ < Footer />
120+ </ div >
121+ ) ;
122+ } ;
123+
124+ export default EventsPage ;
0 commit comments