"use client"; import { useEffect, useState, useCallback, useMemo } from "react"; import dynamic from "next/dynamic"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Slider } from "@/components/ui/slider"; import { CruiserCard } from "@/components/CruiserCard"; import { ProfilePopup } from "@/components/ProfilePopup"; import { Filter, Users, X } from "lucide-react"; import type { Profile, CruisingSpot, MapFilters, Position } from "@/types"; import { KINK_OPTIONS, POSITION_COLORS } from "@/types"; import { MOCK_PROFILES, MOCK_SPOTS } from "@/lib/mock-data"; import "leaflet/dist/leaflet.css"; import "leaflet.markercluster/dist/MarkerCluster.css"; import "leaflet.markercluster/dist/MarkerCluster.Default.css"; const MapContainer = dynamic( () => import("react-leaflet").then((m) => m.MapContainer), { ssr: false } ); const TileLayer = dynamic( () => import("react-leaflet").then((m) => m.TileLayer), { ssr: false } ); const Marker = dynamic( () => import("react-leaflet").then((m) => m.Marker), { ssr: false } ); const Popup = dynamic( () => import("react-leaflet").then((m) => m.Popup), { ssr: false } ); const MarkerClusterGroup = dynamic( () => import("react-leaflet-cluster"), { ssr: false } ); const DEFAULT_LAT = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_LAT || "26.1224"); const DEFAULT_LNG = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_LNG || "-80.1373"); interface MapProps { vanillaMode: boolean; onActiveCountChange?: (count: number) => void; } export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) { const [profiles, setProfiles] = useState(MOCK_PROFILES); const [spots] = useState(MOCK_SPOTS); const [selectedProfile, setSelectedProfile] = useState(null); const [showFilters, setShowFilters] = useState(false); const [showSidebar, setShowSidebar] = useState(true); const [leafletReady, setLeafletReady] = useState(false); const [userIcon, setUserIcon] = useState(null); const [spotIcon, setSpotIcon] = useState(null); const [filters, setFilters] = useState({ ageMin: 18, ageMax: 65, positions: [], kinks: [], onPrep: null, status: [], onlineOnly: true, }); useEffect(() => { import("leaflet").then((L) => { const createUserIcon = (position: string | null, status: string) => { const color = position ? POSITION_COLORS[position as Position] || "#ff2d6b" : "#ff2d6b"; const pulse = status !== "offline" ? "animation:pulse 2s infinite;" : ""; return L.divIcon({ className: "custom-marker", html: `
`, iconSize: [36, 36], iconAnchor: [18, 18], }); }; const createSpotIcon = (category: string) => { const colors: Record = { park: "#22c55e", beach: "#3b82f6", gym: "#f97316", restroom: "#a855f7", bookstore: "#ec4899", club: "#ff2d6b", other: "#6b7280", }; const color = colors[category] || "#6b7280"; return L.divIcon({ className: "spot-marker", html: `
`, iconSize: [28, 28], iconAnchor: [14, 14], }); }; setUserIcon({ create: createUserIcon }); setSpotIcon({ create: createSpotIcon }); setLeafletReady(true); }); }, []); const filteredProfiles = useMemo(() => { return profiles.filter((p) => { if (filters.onlineOnly && p.status === "offline") return false; if (p.age && (p.age < filters.ageMin || p.age > filters.ageMax)) return false; if (filters.positions.length && p.position && !filters.positions.includes(p.position)) return false; if (filters.kinks.length && !filters.kinks.some((k) => p.kinks.includes(k))) return false; if (filters.onPrep === true && !p.on_prep) return false; if (filters.status.length && !filters.status.includes(p.status)) return false; return true; }); }, [profiles, filters]); useEffect(() => { onActiveCountChange?.(filteredProfiles.filter((p) => p.status !== "offline").length); }, [filteredProfiles, onActiveCountChange]); const handleChat = useCallback((profile: Profile) => { window.location.href = `/chat?user=${profile.id}`; }, []); const togglePosition = (pos: Position) => { setFilters((f) => ({ ...f, positions: f.positions.includes(pos) ? f.positions.filter((p) => p !== pos) : [...f.positions, pos], })); }; const toggleKink = (kink: string) => { setFilters((f) => ({ ...f, kinks: f.kinks.includes(kink) ? f.kinks.filter((k) => k !== kink) : [...f.kinks, kink], })); }; return (
{/* Filter bar */}
{filteredProfiles.filter((p) => p.status !== "offline").length} active now
{/* Filter panel */} {showFilters && (
Map Filters
setFilters((f) => ({ ...f, ageMin: min, ageMax: max })) } className="mt-2" />
{(["top", "bottom", "vers", "side"] as Position[]).map((pos) => ( togglePosition(pos)} > {pos} ))}
{KINK_OPTIONS.map((k) => ( toggleKink(k)} > {k} ))}
setFilters((f) => ({ ...f, onPrep: v ? true : null })) } />
setFilters((f) => ({ ...f, onlineOnly: v })) } />
)} {/* Sidebar — active cruisers */} {showSidebar && (

Active Cruisers

{filteredProfiles .filter((p) => p.status !== "offline") .map((p) => ( ))}
)} {/* Map */} {leafletReady && userIcon && spotIcon && ( {filteredProfiles.map((p) => p.lat && p.lng ? ( unknown }).create(p.position, p.status)} eventHandlers={{ click: () => setSelectedProfile(p), }} >
{p.is_anonymous ? "Anonymous" : p.display_name || "Cruiser"} {p.position && ( {p.position} )}

{p.bio}

) : null )}
{spots.map((s) => ( unknown }).create(s.category)} >
{s.name} {s.category}

{s.description}

{s.active_count} active · ⭐ {s.rating}

))}
)} setSelectedProfile(null)} onChat={handleChat} vanillaMode={vanillaMode} />
); }