deafgain-website/src/pages/Services.tsx

413 lines
14 KiB
TypeScript

import { motion, AnimatePresence } from 'framer-motion'
import { ComposableMap, Geographies, Geography, Marker } from 'react-simple-maps'
import { useState, useEffect, useRef } from 'react'
import { FaFileAlt, FaGlobe, FaUsers, FaChalkboardTeacher, FaTimes, FaMapMarkerAlt, FaClock } from 'react-icons/fa'
const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"
type Coordinates = [number, number]
interface Event {
name: string
description: string
startDate: string
endDate: string
}
interface RelationshipPoint {
coordinates: Coordinates
city: string
state: string
events: Event[]
}
const relationshipPoints: RelationshipPoint[] = [
{
coordinates: [-106.6504, 35.0844] as Coordinates,
city: "Albuquerque",
state: "New Mexico",
events: [{
name: "NLTC",
description: "National Leadership Training Conference - NAD",
startDate: "September 21, 2023",
endDate: "September 24, 2023"
}]
},
{
coordinates: [-80.8431, 35.2271] as Coordinates,
city: "Charlotte",
state: "North Carolina",
events: [{
name: "SERID",
description: "Southeast Regional Institute on Deafness Conference",
startDate: "October 26, 2023",
endDate: "October 29, 2023"
}]
},
{
coordinates: [-104.9903, 39.7392] as Coordinates,
city: "Denver",
state: "Colorado",
events: [
{
name: "DBL",
description: "Deaf, Blind, Limitless Conference",
startDate: "March 12, 2024",
endDate: "March 15, 2024"
},
{
name: "EHDI",
description: "Early Hearing Detection Intervention Conference",
startDate: "March 17, 2024",
endDate: "March 19, 2024"
}
]
},
{
coordinates: [-84.3879, 33.7490] as Coordinates,
city: "Atlanta",
state: "Georgia",
events: [{
name: "ADARA",
description: "American Deafness and Rehabilitation Association Conference",
startDate: "May 28, 2024",
endDate: "June 1, 2024"
}]
},
{
coordinates: [-93.2650, 44.9778] as Coordinates,
city: "Minneapolis",
state: "Minnesota",
events: [{
name: "ADA",
description: "Americans with Disabilities Act Conference",
startDate: "June 9, 2024",
endDate: "June 12, 2024"
}]
},
{
coordinates: [-87.6298, 41.8781] as Coordinates,
city: "Chicago",
state: "Illinois",
events: [{
name: "NAD",
description: "National Association of the Deaf Conference",
startDate: "July 1, 2024",
endDate: "July 6, 2024"
}]
},
{
coordinates: [-98.4936, 29.4241] as Coordinates,
city: "San Antonio",
state: "Texas",
events: [{
name: "NAP NTC",
description: "National Association of Parliament - National Training Conference",
startDate: "September 5, 2024",
endDate: "September 8, 2024"
}]
},
{
coordinates: [-89.4012, 43.0731] as Coordinates,
city: "Madison",
state: "Wisconsin",
events: [{
name: "ADA",
description: "Americans with Disabilities Act Conference",
startDate: "October 21, 2024",
endDate: "October 22, 2024"
}]
},
{
coordinates: [-94.8191, 38.8814] as Coordinates,
city: "Olathe",
state: "Kansas",
events: [{
name: "KSD",
description: "Kansas School for the Deaf",
startDate: "November 23, 2024",
endDate: "November 23, 2024"
}]
},
{
coordinates: [-115.1398, 36.1699] as Coordinates,
city: "Las Vegas",
state: "Nevada",
events: [{
name: "Engagement Conference",
description: "2024 Engagement Conference",
startDate: "December 15, 2024",
endDate: "December 15, 2024"
}]
},
{
coordinates: [-112.0740, 33.4484] as Coordinates,
city: "Phoenix",
state: "Arizona",
events: [
{
name: "DANE",
description: "Deaf Abuse Network and Education - Advocacy Round-table Discussion",
startDate: "May 31, 2025",
endDate: "May 31, 2025"
},
{
name: "NAP",
description: "National Association of Parliamentarians - NAP 45th Biennial Convention",
startDate: "August 20, 2025",
endDate: "August 24, 2025"
}
]
},
{
coordinates: [-112.0362, 46.5958] as Coordinates,
city: "Helena",
state: "Montana",
events: [{
name: "AIP",
description: "American Institute of Parliamentarians - 2025 Spring Practicum (Online)",
startDate: "June 12, 2025",
endDate: "June 14, 2025"
}]
},
{
coordinates: [-97.3375, 37.6872] as Coordinates,
city: "Wichita",
state: "Kansas",
events: [{
name: "KAD",
description: "Kansas Association of the Deaf - 2025 General Business Meeting",
startDate: "June 21, 2025",
endDate: "June 21, 2025"
}]
},
{
coordinates: [-91.1871, 30.4515] as Coordinates,
city: "Baton Rouge",
state: "Louisiana",
events: [{
name: "LAD",
description: "Louisiana Association of the Deaf - 2025 Conference",
startDate: "June 26, 2025",
endDate: "June 28, 2025"
}]
},
{
coordinates: [-77.0369, 38.9072] as Coordinates,
city: "Washington",
state: "DC",
events: [{
name: "ASLTA",
description: "American Sign Language Teachers Association - National Professional Development Conference",
startDate: "July 9, 2025",
endDate: "July 12, 2025"
}]
},
{
coordinates: [-94.5786, 39.0997] as Coordinates,
city: "Kansas City",
state: "Missouri",
events: [{
name: "NBDA",
description: "National Black Deaf Advocates - Building Our Legacy: The Road To Success",
startDate: "August 13, 2025",
endDate: "August 16, 2025"
}]
}
]
const services = [
{
icon: <FaFileAlt className="text-4xl" />,
title: "ADA-Compliant Transcripts",
description: "High-quality, accurate transcripts with speaker identification and context differentiation."
},
{
icon: <FaGlobe className="text-4xl" />,
title: "Universal Website Design",
description: "Visually appealing and accessible websites adhering to universal design principles."
},
{
icon: <FaUsers className="text-4xl" />,
title: "Board Communication",
description: "Comprehensive protocol and tools for effective board communication."
},
{
icon: <FaChalkboardTeacher className="text-4xl" />,
title: "Training & Workshops",
description: "Leadership development and parliamentary procedure training."
}
]
const Services = () => {
const [selectedPoint, setSelectedPoint] = useState<typeof relationshipPoints[0] | null>(null)
const popupRef = useRef<HTMLDivElement>(null)
const isUpcoming = (event: { startDate: string, endDate: string }) => {
const eventEndDate = new Date(event.endDate)
const now = new Date()
// Set the end date to the end of the day
eventEndDate.setHours(23, 59, 59, 999)
return eventEndDate > now
}
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
setSelectedPoint(null)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
return (
<div className="min-h-screen">
<section className="py-20 bg-accent-snow">
<div className="max-w-7xl mx-auto px-4">
<h1 className="text-4xl font-bold text-center text-secondary mb-16">
Our Services
</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{services.map((service, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.2 }}
className="bg-white p-6 rounded-lg shadow-lg hover:shadow-xl transition-shadow"
>
<div className="text-primary mb-4">{service.icon}</div>
<h3 className="text-xl font-semibold mb-2">{service.title}</h3>
<p className="text-accent-mountain">{service.description}</p>
</motion.div>
))}
</div>
</div>
</section>
<section className="py-20 bg-white">
<div className="max-w-7xl mx-auto px-4">
<h2 className="text-3xl font-bold text-center text-secondary mb-12">
Our Network & Relationships
</h2>
<div className="relative h-[820px]">
<ComposableMap projection="geoAlbersUsa">
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map(geo => (
<Geography
key={geo.rsmKey}
geography={geo}
fill="#647C90"
stroke="#FFFFFF"
strokeWidth={0.5}
style={{
default: { fill: "#647C90", outline: "none" },
hover: { fill: "#2C4A3E", outline: "none" },
pressed: { fill: "#A4C3D2", outline: "none" }
}}
/>
))
}
</Geographies>
{relationshipPoints.map((point) => {
const hasUpcomingEvent = point.events.some(event => isUpcoming(event))
return (
<Marker key={`${point.city}-${point.state}`} coordinates={point.coordinates}>
<motion.circle
r={6}
fill={hasUpcomingEvent ? "#4aa63f" : "#A4C3D2"}
stroke="#FFFFFF"
strokeWidth={2}
className="cursor-pointer"
whileHover={{ scale: 1.5 }}
onClick={() => setSelectedPoint(point)}
/>
</Marker>
)
})}
</ComposableMap>
<AnimatePresence>
{selectedPoint && (
<motion.div
ref={popupRef}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2
backdrop-blur-lg bg-white/80 p-8 rounded-2xl shadow-2xl
border border-white/20 w-96
hover:shadow-accent-lake/20 hover:shadow-2xl
transition-all duration-300"
style={{
background: 'linear-gradient(145deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.8) 100%)',
}}
>
<motion.button
whileHover={{ rotate: 90 }}
transition={{ duration: 0.2 }}
onClick={() => setSelectedPoint(null)}
className="absolute top-4 right-4 text-accent-mountain hover:text-secondary
bg-white/50 p-2 rounded-full"
>
<FaTimes />
</motion.button>
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.1 }}
>
<h3 className="text-2xl font-bold text-secondary mb-2 flex items-center gap-2">
<FaMapMarkerAlt className="text-primary" />
{selectedPoint.city}, {selectedPoint.state}
</h3>
<div className="space-y-4 mt-6">
{selectedPoint.events
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
.map((event, index) => (
<motion.div
key={index}
initial={{ x: -20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: index * 0.1 }}
className={`
relative overflow-hidden rounded-xl
${index !== 0 ? "mt-4" : ""}
bg-gradient-to-r from-white/90 to-white/80
p-4 hover:from-white/95 hover:to-white/85
transition-all duration-300
border border-white/30
`}
>
<div className="relative z-10">
<h4 className="font-bold text-secondary text-lg mb-1">{event.name}</h4>
<p className="text-accent-mountain text-sm mb-2 font-medium">{event.description}</p>
<div className="flex items-center gap-2 text-sm text-primary font-semibold">
<FaClock className="text-xs" />
{event.startDate === event.endDate
? event.startDate
: `${event.startDate} - ${event.endDate}`}
</div>
</div>
<div className="absolute inset-0 bg-gradient-to-r from-primary/5 to-secondary/5" />
</motion.div>
))}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</section>
</div>
)
}
export default Services