fix(cf-modal): portal to body + auto-close on parcel switch
Two related issues with the modal when the user kept clicking around the map while in CF order mode: 1. LAYOUT BREAK (Marius screenshot — modal header clipped above viewport): The V2 panel wrapper uses `backdrop-blur-md`. Per CSS spec, an element with non-none backdrop-filter establishes a containing block for `fixed`-positioned descendants. So `fixed inset-0` on the modal was relative to the panel (top-right, ~50px tall at min) instead of the viewport — the modal anchored to the panel and overflowed up. Fix: render via React's createPortal to document.body. The modal now escapes the panel's stacking context entirely and centers in the viewport. Also bumped z-index from 50 to 100 so the modal stays above the MapLibre canvas + panel itself. 2. STATE CARRY-OVER: clicking a different parcel while the modal was open silently re-targeted the modal at the new parcel — same modal showing different cadref/sold mid-flow could mislead the user about which parcel they were buying CF for. Fix: FeatureInfoPanel now has a useEffect that closes the modal when feature.cadastralRef / siruta / layerId changes. Modal stays scoped to a single decision. SSR guard: if (typeof document === "undefined") return null; before the portal call so the modal doesn't blow up during server-side render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
// error → any failure with retry option
|
// error → any failure with retry option
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import {
|
import {
|
||||||
X, Loader2, Check, AlertCircle, FileText, Coins,
|
X, Loader2, Check, AlertCircle, FileText, Coins,
|
||||||
Plug, ArrowRight, Download,
|
Plug, ArrowRight, Download,
|
||||||
@@ -276,12 +277,18 @@ export function CfOrderModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
// Render via portal so the modal escapes the panel's containing
|
||||||
|
// block — the parent V2 panel uses backdrop-blur-md which (per CSS
|
||||||
|
// spec) creates a containing block for `fixed` descendants. Without
|
||||||
|
// the portal the modal anchors to the panel's top-right corner and
|
||||||
|
// gets clipped above the viewport.
|
||||||
|
if (typeof document === "undefined") return null;
|
||||||
|
|
||||||
return (
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-4 backdrop-blur-sm animate-in fade-in duration-200"
|
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 px-4 backdrop-blur-sm animate-in fade-in duration-200"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// dismiss on backdrop click only when not mid-flight
|
// dismiss on backdrop click only when not mid-flight
|
||||||
if (
|
if (
|
||||||
@@ -602,7 +609,8 @@ export function CfOrderModal({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
document.body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,13 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
|||||||
const [condoLoading, setCondoLoading] = useState(false);
|
const [condoLoading, setCondoLoading] = useState(false);
|
||||||
const [cfModalOpen, setCfModalOpen] = useState(false);
|
const [cfModalOpen, setCfModalOpen] = useState(false);
|
||||||
|
|
||||||
|
// Close the CF modal whenever the user switches to a different
|
||||||
|
// parcel — keeps the modal scoped to a single decision instead of
|
||||||
|
// silently re-targeting mid-flight.
|
||||||
|
useEffect(() => {
|
||||||
|
setCfModalOpen(false);
|
||||||
|
}, [feature.cadastralRef, feature.siruta, feature.layerId]);
|
||||||
|
|
||||||
const authRetriedRef = useRef<boolean>(
|
const authRetriedRef = useRef<boolean>(
|
||||||
typeof window !== "undefined" &&
|
typeof window !== "undefined" &&
|
||||||
sessionStorage.getItem(AUTH_RETRY_KEY) === "1",
|
sessionStorage.getItem(AUTH_RETRY_KEY) === "1",
|
||||||
|
|||||||
Reference in New Issue
Block a user