diff --git a/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx b/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx index 4863aa05..da60e5b2 100644 --- a/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx +++ b/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx @@ -1,12 +1,13 @@ "use client"; -import { useEffect } from "react"; -import { usePathname } from "next/navigation"; +import { Suspense, useEffect, useRef } from "react"; +import { usePathname, useSearchParams } from "next/navigation"; import { WhatsNewPanel } from "./WhatsNewPanel"; import { useWhatsNewState, openWhatsNew } from "@/lib/whatsNewStore"; import { trackEvent } from "@/lib/appInsights"; import { isPublicPath } from "@/lib/hostRouting"; import { WHATS_NEW_CHANNELS, type WhatsNewChannel } from "@/lib/whatsNew"; +import { useAuth } from "@/contexts/AuthContext"; /** * Mounts the What's new panel once, in the root layout, so the portal navbar, the landing @@ -24,16 +25,43 @@ export function WhatsNewPanelHost() { const pathname = usePathname(); const onPublicPage = isPublicPath(pathname); + return ( + <> + {/* useSearchParams() needs a Suspense boundary for the static prerender. */} + + + + {panelOpen && } + + ); +} + +/** + * Opens the panel once each time `?whats-new=` arrives, not only on the first mount: a portal + * link opened without a session goes through the sign-in, which lands on the origin root and + * reaches the link again by a client-side navigation (AuthGate), where this layout stays + * mounted. A protected page waits for the session, so the panel neither flashes before the + * sign-in redirect nor counts the open twice. + */ +function WhatsNewDeepLink({ onPublicPage }: { onPublicPage: boolean }) { + const requested = useSearchParams()?.get("whats-new") ?? null; + const { isAuthenticated, isLoading } = useAuth(); + const ready = onPublicPage || (isAuthenticated && !isLoading); + const handled = useRef(false); + useEffect(() => { - const requested = new URLSearchParams(window.location.search).get("whats-new"); - if (requested === null) return; + if (requested === null) { + handled.current = false; + return; + } + if (handled.current || !ready) return; + handled.current = true; const channel = (WHATS_NEW_CHANNELS as readonly string[]).includes(requested) ? (requested as WhatsNewChannel) : undefined; trackEvent("whats_new_opened", { source: "deeplink", channel: channel ?? "default" }); openWhatsNew("deeplink", channel); - }, []); + }, [requested, ready]); - if (!panelOpen) return null; - return ; + return null; }