diff --git a/src/modules/geoportal/v2/feature-info-panel.tsx b/src/modules/geoportal/v2/feature-info-panel.tsx index 3e20754..90b8df4 100644 --- a/src/modules/geoportal/v2/feature-info-panel.tsx +++ b/src/modules/geoportal/v2/feature-info-panel.tsx @@ -1,13 +1,20 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { signIn } from "next-auth/react"; import { X, RefreshCw, Loader2, FileText, Download, AlertCircle, - Home, Building, MapPin, ChevronDown, ChevronRight, Users, LogIn, + Home, Building, MapPin, ChevronDown, ChevronRight, Users, } from "lucide-react"; import { cn } from "@/shared/lib/utils"; +// Once-per-tab marker: when we trigger a silent re-grant via signIn() the +// page reloads through Authentik. After the round-trip we land back on the +// same panel; we don't want to retry the re-grant in an infinite loop if +// Authentik actually can't extend the scope, so we set a sessionStorage +// flag before redirecting and check it on mount. +const AUTH_RETRY_KEY = "gis_panel_auth_retry"; + export interface ClickedFeatureLite { /** GisFeature uuid — typically empty from PMTiles overview, falls back to search-by-cadref */ id: string; @@ -152,6 +159,13 @@ export function FeatureInfoPanel({ feature, onClose, basic = false }: Props) { const [condoOwners, setCondoOwners] = useState(null); const [condoLoading, setCondoLoading] = useState(false); const [condoExpanded, setCondoExpanded] = useState(false); + // Tracks whether we've already triggered a silent re-grant for this tab, + // so a real Authentik scope misconfig falls through to the muted error + // state instead of cycling forever through signIn(). + const authRetriedRef = useRef( + typeof window !== "undefined" && + sessionStorage.getItem(AUTH_RETRY_KEY) === "1", + ); const isCladiri = feature.layerId === "CLADIRI_ACTIVE"; @@ -186,10 +200,28 @@ export function FeatureInfoPanel({ feature, onClose, basic = false }: Props) { return; } if (r.status === 403) { + // Token reaches gis-api but lacks enrichment_scope. Trigger a + // silent fresh OIDC grant — for SSO'd users Authentik is a + // sub-second redirect, the panel re-mounts with full scope and + // shows real data. authRetriedRef guards against infinite loop + // if Authentik genuinely can't grant the scope. + if (!authRetriedRef.current && typeof window !== "undefined") { + authRetriedRef.current = true; + sessionStorage.setItem(AUTH_RETRY_KEY, "1"); + void signIn("authentik", { callbackUrl: window.location.href }); + // Keep "loading" while the browser navigates — no message + // needed; the redirect itself is the feedback. + return; + } setError("forbidden"); setLoading(false); return; } + // Successful fetch (200) — clear the retry marker so a future 403 + // can re-trigger silently. + if (typeof window !== "undefined") { + sessionStorage.removeItem(AUTH_RETRY_KEY); + } if (!r.ok) { setError("fetch_failed"); setLoading(false); @@ -409,26 +441,13 @@ export function FeatureInfoPanel({ feature, onClose, basic = false }: Props) { )} + {/* Forbidden after a silent re-grant attempt already failed + once — likely Authentik scope misconfig. Stays discreet (no + call-to-action), only diag info for the operator. */} {error === "forbidden" && ( -
-
- - - Token-ul tău nu poartă scope-ul „enrichment" (gis-api a - răspuns 403 pentru toate candidate-le). Re-loghează-te ca - să primești un access_token cu scope-ul corect. - -
- +
+ + Datele detaliate nu pot fi încărcate momentan.
)} {error && error !== "forbidden" && (