"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 { CruiserProfileView } from "@/components/CruiserProfileView"; import { GuestUpgradeBanner } from "@/components/GuestUpgradeBanner"; import { Filter, X, Locate } from "lucide-react"; import type { Profile, CruisingSpot, MapFilters, Position } from "@/types"; import { KINK_OPTIONS } from "@/types"; import { MOCK_PROFILES, MOCK_SPOTS } from "@/lib/mock-data"; import { useOpenIdVerification, useAccess } from "@/components/Providers"; import { distanceMiles } from "@/lib/profile-stats"; import "leaflet/dist/leaflet.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 DEFAULT_LAT = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_LAT || "26.1224"); const DEFAULT_LNG = parseFloat(process.env.NEXT_PUBLIC_DEFAULT_LNG || "-80.1373"); const DARK_TILES = "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"; interface MapProps { vanillaMode: boolean; onActiveCountChange?: (count: number) => void; } export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) { const openIdVerification = useOpenIdVerification(); const { capabilities, blurExplicit } = useAccess(); const [profiles] = useState(MOCK_PROFILES); const [spots] = useState(MOCK_SPOTS); const [selectedProfile, setSelectedProfile] = useState(null); const [showFilters, setShowFilters] = useState(false); const [leafletReady, setLeafletReady] = useState(false); const [iconFactory, setIconFactory] = useState<{ createPhoto: (p: Profile, blur: boolean) => import("leaflet").DivIcon; createYou: () => import("leaflet").DivIcon; createSpot: (category: string, count: number) => import("leaflet").DivIcon; } | null>(null); const [filters, setFilters] = useState({ ageMin: 18, ageMax: 65, positions: [], kinks: [], onPrep: null, status: [], onlineOnly: true, }); useEffect(() => { import("leaflet").then((L) => { const createPhoto = (p: Profile, blur: boolean) => { const url = p.avatar_url || "https://placehold.co/96x96/0a1628/3b82f6?text=?"; const dist = p.distance_miles ?? (p.lat && p.lng ? distanceMiles(DEFAULT_LAT, DEFAULT_LNG, p.lat, p.lng) : 1); const distLabel = dist < 1 ? dist.toFixed(1) : String(Math.max(1, Math.round(dist))); const blurStyle = blur ? "filter:blur(6px);" : ""; const safeUrl = url.replace(/'/g, "%27"); return L.divIcon({ className: "sniffies-marker-wrap", html: `
${distLabel}
`, iconSize: [52, 58], iconAnchor: [26, 29], }); }; const createYou = () => L.divIcon({ className: "sniffies-you-wrap", html: `
You
`, iconSize: [48, 56], iconAnchor: [24, 28], }); const createSpot = (category: string, count: number) => { const icons: Record = { club: "🍸", gym: "💪", beach: "🏖", park: "🌳", bookstore: "📚", restroom: "🚻", other: "📍", }; const emoji = icons[category] || "📍"; return L.divIcon({ className: "sniffies-spot-wrap", html: `
${emoji} ${count > 0 ? `
${count}
` : ""}
`, iconSize: [36, 36], iconAnchor: [18, 18], }); }; setIconFactory({ createPhoto, createYou, createSpot }); 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) => { if (!capabilities.canSendMessages) return; window.location.href = `/chat?user=${profile.id}`; }, [capabilities.canSendMessages]); 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 (
{/* Minimal top controls — Sniffies style */}
{filteredProfiles.filter((p) => p.status !== "offline").length} cruising
{!capabilities.canSendMessages && (
)}
{showFilters && (
Filters
setFilters((f) => ({ ...f, ageMin: min, ageMax: max })) } className="mt-2" />
{(["top", "bottom", "vers", "side"] as Position[]).map((pos) => ( togglePosition(pos)} > {pos} ))}
{KINK_OPTIONS.slice(0, 12).map((k) => ( toggleKink(k)} > {k} ))}
setFilters((f) => ({ ...f, onPrep: v ? true : null })) } />
setFilters((f) => ({ ...f, onlineOnly: v })) } />
)} {/* Guest banner mobile */} {!capabilities.canSendMessages && (
)} {leafletReady && iconFactory && ( {filteredProfiles.map((p) => p.lat && p.lng ? ( setSelectedProfile(p), }} /> ) : null )} {spots.map((s) => ( { /* spot detail future */ }, }} /> ))} )} {/* Recenter */} setSelectedProfile(null)} onChat={handleChat} vanillaMode={vanillaMode} onVerifyClick={openIdVerification} />
); }