"use client" import { useEffect, useRef, useState } from "react" import mapboxgl from "mapbox-gl" import "mapbox-gl/dist/mapbox-gl.css" import { MapPin } from "lucide-react" type LngLat = { lng: number; lat: number } /** * MapPointPicker * * Rendered ONLY when the user chooses "Velg punkt på kart", and always via * next/dynamic(..., { ssr: false }) from the parent. It mounts once per * sheet-open and stays mounted until the sheet closes, so the Mapbox map is * created exactly once (empty-deps useEffect) — taps, pans and marker drags * never re-initialise it. This keeps Mapbox map loads (which are billed) to a * single load per sheet-open. */ export function MapPointPicker({ onConfirm }: { onConfirm: (lngLat: LngLat) => void }) { const containerRef = useRef(null) const mapRef = useRef(null) const markerRef = useRef(null) const [loaded, setLoaded] = useState(false) const [point, setPoint] = useState(null) const [error, setError] = useState(null) useEffect(() => { const token = process.env.NEXT_PUBLIC_MAPBOX_TOKEN const container = containerRef.current if (!container) return if (!token) { setError("Kartet er ikke tilgjengelig akkurat nå. Bruk «Min posisjon nå» i stedet.") return } let cancelled = false // Reell bug funnet 2026-08-08: kartet sentrerte ALLTID på en hardkodet // Oslo-koordinat -- en kommentar her hevdet "real app centers on // last-known position", men den logikken fantes aldri, kun påstanden. // Henter nå brukerens faktiske GPS-posisjon FØR kartet initialiseres // (kun ETT kall, samme som "Min posisjon nå"-veien -- ingen løpende // watchPosition), med Oslo som fallback KUN hvis posisjon ikke kan // hentes (avslått tillatelse, tidsavbrudd, ingen støtte). function initMap(center: [number, number]) { if (cancelled || !container) return mapboxgl.accessToken = token as string const map = new mapboxgl.Map({ container, style: "mapbox://styles/mapbox/satellite-v9", center, zoom: 16, attributionControl: false, }) mapRef.current = map map.on("load", () => setLoaded(true)) map.on("error", () => setError("Kunne ikke laste kartet.")) // Tap to place/move a single draggable marker — never re-inits the map. map.on("click", (e) => { const lngLat = { lng: e.lngLat.lng, lat: e.lngLat.lat } if (!markerRef.current) { const marker = new mapboxgl.Marker({ draggable: true, color: "#d2551a" }) .setLngLat(e.lngLat) .addTo(map) marker.on("dragend", () => { const p = marker.getLngLat() setPoint({ lng: p.lng, lat: p.lat }) }) markerRef.current = marker } else { markerRef.current.setLngLat(e.lngLat) } setPoint(lngLat) }) } const OSLO_FALLBACK: [number, number] = [10.7522, 59.9139] if (typeof navigator !== "undefined" && navigator.geolocation) { navigator.geolocation.getCurrentPosition( (pos) => initMap([pos.coords.longitude, pos.coords.latitude]), () => initMap(OSLO_FALLBACK), { enableHighAccuracy: true, timeout: 8000 }, ) } else { initMap(OSLO_FALLBACK) } return () => { cancelled = true markerRef.current?.remove() markerRef.current = null mapRef.current?.remove() mapRef.current = null } }, []) return (
{/* Map canvas host — kept mounted for the map's whole lifetime. `h-full w-full`, IKKE `absolute inset-0`: mapbox-gl.css sin egen `.mapboxgl-map { position: relative }`-regel overstyrer en `absolute`-Tailwind-klasse her (samme klasse-navn, senere i cascade siden mapbox-gl.css importeres i denne komponentfilen) -- når posisjonen dermed blir `relative` i stedet for `absolute`, mister `inset-0` all effekt på størrelsen (den styrer kun posisjon/størrelse for absolutt/fixed-plasserte elementer), og beholderen kollapser til 0 høyde uansett hvilken posisjonsverdi som vinner kappløpet. `h-full`/`w-full` løser størrelsen via prosent-arv i stedet, som fungerer likt for både `relative` og `absolute`. Reell bug funnet 2026-08-08, se CHANGELOG.md. */}
{!loaded && !error ? (

Laster kart …

) : null} {error ? (

{error}

) : null} {loaded && !point && !error ? (

) : null}
) } export default MapPointPicker