initial
This commit is contained in:
164
web/components/InteractiveMap.tsx
Normal file
164
web/components/InteractiveMap.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface Location {
|
||||
name: string;
|
||||
coordinates: [number, number];
|
||||
description: string;
|
||||
services: string[];
|
||||
responseTime: string;
|
||||
}
|
||||
|
||||
const locations: Location[] = [
|
||||
{
|
||||
name: 'Corpus Christi',
|
||||
coordinates: [27.8006, -97.3964],
|
||||
description: 'Main service area - 24/7 emergency response',
|
||||
services: ['Emergency Repair', 'Panel Upgrades', 'EV Chargers', 'Commercial'],
|
||||
responseTime: '< 30 minutes'
|
||||
},
|
||||
{
|
||||
name: 'Flour Bluff',
|
||||
coordinates: [27.6831, -97.2201],
|
||||
description: 'Residential and commercial electrical services',
|
||||
services: ['Emergency Repair', 'Panel Upgrades', 'Lighting'],
|
||||
responseTime: '< 45 minutes'
|
||||
},
|
||||
{
|
||||
name: 'Portland',
|
||||
coordinates: [27.8772, -97.3239],
|
||||
description: 'Full electrical contractor services',
|
||||
services: ['Emergency Repair', 'Panel Upgrades', 'Commercial'],
|
||||
responseTime: '< 60 minutes'
|
||||
},
|
||||
{
|
||||
name: 'Aransas Pass',
|
||||
coordinates: [27.9095, -97.1499],
|
||||
description: 'Coastal electrical services',
|
||||
services: ['Emergency Repair', 'Panel Upgrades', 'Marine Electrical'],
|
||||
responseTime: '< 60 minutes'
|
||||
},
|
||||
{
|
||||
name: 'Rockport',
|
||||
coordinates: [28.0206, -97.0544],
|
||||
description: 'Coastal and residential electrical',
|
||||
services: ['Emergency Repair', 'Panel Upgrades', 'Outdoor Lighting'],
|
||||
responseTime: '< 75 minutes'
|
||||
}
|
||||
];
|
||||
|
||||
export default function InteractiveMap() {
|
||||
const [selectedLocation, setSelectedLocation] = useState<Location | null>(null);
|
||||
const [hoveredLocation, setHoveredLocation] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Map Container */}
|
||||
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-3xl h-96 relative overflow-hidden shadow-xl">
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg"%3E%3Cg fill="none" fill-rule="evenodd"%3E%3Cg fill="%23000000" fill-opacity="0.03"%3E%3Ccircle cx="30" cy="30" r="2"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')]"></div>
|
||||
|
||||
{/* Service Area Circle */}
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-gradient-to-br from-brand-green/20 to-green-600/20 rounded-full border-2 border-brand-green/30 animate-pulse-slow"></div>
|
||||
|
||||
{/* Location Markers */}
|
||||
{locations.map((location, index) => (
|
||||
<div
|
||||
key={location.name}
|
||||
className={`absolute transform -translate-x-1/2 -translate-y-1/2 cursor-pointer transition-all duration-300 ${
|
||||
hoveredLocation === location.name ? 'scale-125' : 'scale-100'
|
||||
}`}
|
||||
style={{
|
||||
left: `${20 + (index * 15)}%`,
|
||||
top: `${30 + (index * 10)}%`
|
||||
}}
|
||||
onMouseEnter={() => setHoveredLocation(location.name)}
|
||||
onMouseLeave={() => setHoveredLocation(null)}
|
||||
onClick={() => setSelectedLocation(location)}
|
||||
>
|
||||
{/* Marker */}
|
||||
<div className={`w-4 h-4 bg-gradient-to-r from-brand-green to-green-600 rounded-full border-2 border-white shadow-lg ${
|
||||
selectedLocation?.name === location.name ? 'ring-4 ring-brand-green/50' : ''
|
||||
}`}></div>
|
||||
|
||||
{/* Location Name */}
|
||||
<div className={`absolute top-6 left-1/2 transform -translate-x-1/2 whitespace-nowrap bg-white px-3 py-1 rounded-full text-xs font-semibold shadow-md transition-all duration-300 ${
|
||||
hoveredLocation === location.name ? 'opacity-100 scale-100' : 'opacity-0 scale-95'
|
||||
}`}>
|
||||
{location.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Center Point */}
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-6 h-6 bg-gradient-to-r from-brand-green to-green-600 rounded-full border-4 border-white shadow-xl animate-bounce-gentle"></div>
|
||||
|
||||
{/* Map Title */}
|
||||
<div className="absolute top-6 left-6 bg-white/90 backdrop-blur-sm px-4 py-2 rounded-full shadow-md">
|
||||
<h3 className="font-semibold text-gray-800">Service Coverage Area</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Location Info Panel */}
|
||||
{selectedLocation && (
|
||||
<div className="absolute -bottom-4 left-1/2 transform -translate-x-1/2 w-full max-w-md animate-slide-up">
|
||||
<div className="bg-white rounded-2xl shadow-2xl p-6 border border-gray-100">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h3 className="font-heading font-bold text-xl text-gradient">
|
||||
{selectedLocation.name}
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setSelectedLocation(null)}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-4">{selectedLocation.description}</p>
|
||||
|
||||
<div className="mb-4">
|
||||
<h4 className="font-semibold text-gray-800 mb-2">Services Available:</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedLocation.services.map((service) => (
|
||||
<span key={service} className="bg-brand-green/10 text-brand-green px-2 py-1 rounded-full text-xs font-medium">
|
||||
{service}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-sm text-gray-500">Response Time:</span>
|
||||
<p className="font-semibold text-brand-green">{selectedLocation.responseTime}</p>
|
||||
</div>
|
||||
<a
|
||||
href="tel:+13618850315"
|
||||
className="btn-primary btn-sm"
|
||||
>
|
||||
Call Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Legend */}
|
||||
<div className="absolute bottom-4 right-4 bg-white/90 backdrop-blur-sm px-4 py-3 rounded-xl shadow-md">
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-gradient-to-r from-brand-green to-green-600 rounded-full"></div>
|
||||
<span className="text-gray-700">Service Area</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-gradient-to-r from-brand-green to-green-600 rounded-full border-2 border-white"></div>
|
||||
<span className="text-gray-700">Locations</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user