From 1c4e178b390c66fc3b8951cff34da004fc428a71 Mon Sep 17 00:00:00 2001 From: Snow Lee Date: Mon, 13 Jul 2026 10:07:30 -0700 Subject: [PATCH] feat(webapp): [phase 1] shell, session, projects, routing - URL as source of truth: parseRoute/urlForPath/urlForView ported verbatim (src/router.ts), single catch-all route so encoded slashes survive - hub shell: project nav with color chips, org bar, admin bar with pending count, sign-out per session flags; volume shell renders too - empty-state onboarding (invite paste + create project), /join/ invite accept that survives the login redirect - toast + modal prompt/confirm primitives (imperative promise API over a React host, matching the classic behavior) - e2e: 7 new hub specs (11 total green); harness gains a no-org 'solo' account to reach the empty state Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt --- docs/react-migration-prd.md | 21 ++- internal/webapp/e2e_serve_test.go | 6 + internal/webapp/frontend/e2e/hub.spec.ts | 98 ++++++++++ internal/webapp/frontend/e2e/shell.spec.ts | 2 +- internal/webapp/frontend/src/App.tsx | 43 ++--- internal/webapp/frontend/src/api/types.ts | 35 ++++ internal/webapp/frontend/src/apps/HubApp.tsx | 168 ++++++++++++++++++ .../webapp/frontend/src/apps/VolumeApp.tsx | 21 +++ .../frontend/src/components/EmptyState.tsx | 55 ++++++ .../webapp/frontend/src/components/OrgBar.tsx | 32 ++++ .../frontend/src/components/ProjectNav.tsx | 72 ++++++++ .../webapp/frontend/src/components/shell.tsx | 125 +++++++++++++ internal/webapp/frontend/src/hooks/useHub.ts | 49 +++++ internal/webapp/frontend/src/modal.tsx | 141 +++++++++++++++ internal/webapp/frontend/src/router.ts | 64 +++++++ internal/webapp/frontend/src/toast.tsx | 37 ++++ .../webapp/static/assets/index-BoqNtp9Y.js | 11 ++ .../webapp/static/assets/index-CZjUv2DS.js | 11 -- internal/webapp/static/index.html | 2 +- 19 files changed, 947 insertions(+), 46 deletions(-) create mode 100644 internal/webapp/frontend/e2e/hub.spec.ts create mode 100644 internal/webapp/frontend/src/apps/HubApp.tsx create mode 100644 internal/webapp/frontend/src/apps/VolumeApp.tsx create mode 100644 internal/webapp/frontend/src/components/EmptyState.tsx create mode 100644 internal/webapp/frontend/src/components/OrgBar.tsx create mode 100644 internal/webapp/frontend/src/components/ProjectNav.tsx create mode 100644 internal/webapp/frontend/src/components/shell.tsx create mode 100644 internal/webapp/frontend/src/hooks/useHub.ts create mode 100644 internal/webapp/frontend/src/modal.tsx create mode 100644 internal/webapp/frontend/src/router.ts create mode 100644 internal/webapp/frontend/src/toast.tsx create mode 100644 internal/webapp/static/assets/index-BoqNtp9Y.js delete mode 100644 internal/webapp/static/assets/index-CZjUv2DS.js diff --git a/docs/react-migration-prd.md b/docs/react-migration-prd.md index 1c01982..0e7e5db 100644 --- a/docs/react-migration-prd.md +++ b/docs/react-migration-prd.md @@ -121,11 +121,20 @@ refresh; invalidate after uploads/renames/admin actions — mirror today's - [x] `go build ./...`, `go vet ./...`, `go test ./...` green. ### Phase 1 — shell: boot, session, projects, routing -- [ ] Boot from `/api/config`; volume vs hub mode both render. -- [ ] Project list + selection; project color chips (port `projColor`). -- [ ] Empty state + create project; `/join/` invite accept. -- [ ] Deep link + refresh on every route resolves (SPA fallback). -- [ ] Sign-out link, admin bar, org bar render per session flags. +- [x] Boot from `/api/config`; volume vs hub mode both render (hub via the + e2e suite; volume verified against a live `bdrive web `). +- [x] Project list + selection; project color chips (port `projColor`). +- [x] Empty state + create project; `/join/` invite accept (token + survives the login redirect — covered by e2e). +- [x] Deep link + refresh on every route resolves (SPA fallback); unknown + project ids fall back to a real project. +- [x] Sign-out link, admin bar (with pending count), org bar render per + session flags (admin vs member covered by e2e). + Architecture note: the URL is the source of truth — `parseRoute` + ported verbatim into `src/router.ts`, a single catch-all route, no + route-matching library (encoded slashes must survive). Mutations + must `await useHubRefresh()` before navigating to a new project id, + or the unknown-id fallback bounces off the stale list. ### Phase 2 — file browsing (long pole) - [ ] Tree with expansion persistence, active marking, reveal-in-tree. @@ -216,7 +225,7 @@ file path; reload on `/insights`. ## Status - [x] Phase 0 -- [ ] Phase 1 +- [x] Phase 1 - [ ] Phase 2 - [ ] Phase 3 - [ ] Phase 4 diff --git a/internal/webapp/e2e_serve_test.go b/internal/webapp/e2e_serve_test.go index c0c06c4..f16053b 100644 --- a/internal/webapp/e2e_serve_test.go +++ b/internal/webapp/e2e_serve_test.go @@ -28,6 +28,7 @@ const ( e2eAddr = "0.0.0.0:8993" e2eAdmin = "e2e@example.com" e2eMember = "member@example.com" + e2eSolo = "solo@example.com" e2ePassword = "e2e-pass-1" ) @@ -76,6 +77,11 @@ func TestE2EServe(t *testing.T) { if _, err := auth.signup(e2eMember, "E2E Member", e2ePassword); err != nil { t.Fatal(err) } + // In no org: sees the onboarding empty state (and creating a project + // from it mints a fresh org via orgForCreate). + if _, err := auth.signup(e2eSolo, "E2E Solo", e2ePassword); err != nil { + t.Fatal(err) + } auth.Admins = map[string]bool{e2eAdmin: true} srv.Auth = auth diff --git a/internal/webapp/frontend/e2e/hub.spec.ts b/internal/webapp/frontend/e2e/hub.spec.ts new file mode 100644 index 0000000..fcdef90 --- /dev/null +++ b/internal/webapp/frontend/e2e/hub.spec.ts @@ -0,0 +1,98 @@ +import { test, expect, Page } from "@playwright/test"; +import { login, MEMBER, PASSWORD } from "./helpers"; + +// Phase 1: shell, session flags, project list/selection, routing, empty +// state, invite accept. Mutating specs (project creation) run last — +// specs share one seeded hub per run. + +async function wikiId(page: Page): Promise { + const out = await (await page.request.get("/api/projects")).json(); + return out.projects.find((p: { name: string }) => p.name === "wiki").id; +} + +test("landing selects the first project and rewrites the URL", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.waitForURL("/" + pid); + await expect(page.locator("#vault-name")).toHaveText("wiki"); + await expect(page).toHaveTitle("wiki — BearDrive"); + await expect(page.locator("#projects .row.active .label")).toHaveText("wiki"); +}); + +test("deep link to a project resolves after reload", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto("/" + pid); + await expect(page.locator("#vault-name")).toHaveText("wiki"); + await expect(page).toHaveURL("/" + pid); +}); + +test("unknown project id falls back to a real project", async ({ page }) => { + await login(page); + await page.goto("/p-00000000"); + await page.waitForURL(/\/p-[0-9a-f]{8}$/); + await expect(page.locator("#vault-name")).not.toHaveText("…"); +}); + +test("admin sees admin bar and org Manage; member does not", async ({ page, browser }) => { + await login(page); // admin, owner of "default" + await expect(page.locator("#adminbar")).toBeVisible(); + await expect(page.locator("#orgbar #org-name")).toHaveText("default"); + await expect(page.locator("#invite-btn")).toBeVisible(); + await expect(page.locator("#signout")).toBeVisible(); + + const ctx = await browser.newContext(); + const p2 = await ctx.newPage(); + await login(p2, MEMBER); + await expect(p2.locator("#orgbar #org-name")).toHaveText("default"); + await expect(p2.locator("#adminbar")).toHaveCount(0); + await expect(p2.locator("#invite-btn")).toHaveCount(0); + await ctx.close(); +}); + +test("join link accepts an invite after sign-in", async ({ page, browser }) => { + await login(page); // admin mints the invite + const orgs = await (await page.request.get("/api/orgs")).json(); + const org = orgs.orgs.find((o: { name: string }) => o.name === "default"); + const inv = await ( + await page.request.post(`/api/orgs/${org.id}/invites`, { data: {} }) + ).json(); + expect(inv.url).toContain("/join/"); + const token = inv.url.split("/join/")[1]; + + // A signed-out visitor keeps the token through the login redirect. + const ctx = await browser.newContext(); + const p2 = await ctx.newPage(); + await p2.goto("/join/" + token); + await p2.waitForURL(/auth\/login/); + await p2.fill('input[name="email"]', MEMBER); + await p2.fill('input[name="password"]', PASSWORD); + await p2.click("form button"); + await p2.waitForSelector("#toast.show"); + await expect(p2.locator("#toast")).toContainText("you joined"); + await p2.waitForURL(/\/p-[0-9a-f]{8}$/); // lands on the org's project + await ctx.close(); +}); + +test("no-org account gets the onboarding empty state and can create a project", async ({ + page, +}) => { + await login(page, "solo@example.com"); + await expect(page.locator(".onboard h1")).toHaveText("Welcome to BearDrive"); + await page.fill("#ob-name", "solo-notes"); + await page.click("#ob-create"); + await page.waitForURL(/\/p-[0-9a-f]{8}$/); + await expect(page.locator("#vault-name")).toHaveText("solo-notes"); + await expect(page.locator("#orgbar")).toBeVisible(); // fresh org, owner +}); + +test("new project via the sidebar + modal", async ({ page }) => { + await login(page); + await page.click("#projects .nav-add"); + await page.fill(".modal-input", "scratch"); + await page.click(".modal .pbtn"); + await page.waitForURL(/\/p-[0-9a-f]{8}$/); + await expect(page.locator("#vault-name")).toHaveText("scratch"); + await expect(page.locator("#projects .row .label")).toContainText(["scratch", "wiki"]); + await expect(page.locator("#toast")).toContainText("Created"); +}); diff --git a/internal/webapp/frontend/e2e/shell.spec.ts b/internal/webapp/frontend/e2e/shell.spec.ts index 99beaf1..6ed95c1 100644 --- a/internal/webapp/frontend/e2e/shell.spec.ts +++ b/internal/webapp/frontend/e2e/shell.spec.ts @@ -9,7 +9,7 @@ test("unauthenticated visit redirects to the login page", async ({ page }) => { test("login lands in the app shell", async ({ page }) => { await login(page); - await expect(page).toHaveTitle("BearDrive"); + await expect(page).toHaveTitle(/BearDrive/); // " — BearDrive" in hub mode await expect(page.locator("#sidebar")).toBeVisible(); await expect(page.locator("#topbar")).toBeVisible(); }); diff --git a/internal/webapp/frontend/src/App.tsx b/internal/webapp/frontend/src/App.tsx index 1983dd2..625d44c 100644 --- a/internal/webapp/frontend/src/App.tsx +++ b/internal/webapp/frontend/src/App.tsx @@ -1,37 +1,26 @@ -import { useEffect } from "react"; import { useConfig } from "./hooks/useConfig"; +import { AppShell, Topbar, VaultHeader } from "./components/shell"; +import { Toaster } from "./toast"; +import { ModalHost } from "./modal"; +import HubApp from "./apps/HubApp"; +import VolumeApp from "./apps/VolumeApp"; -// Phase 0 shell: boots /api/config (redirecting to /auth/login when there -// is no session) and renders the static layout so the ported stylesheet can -// be verified. Routing and data views arrive in Phase 1. export default function App() { const { data: config } = useConfig(); - useEffect(() => { - if (config) document.title = config.brand || config.volume || "BearDrive"; - }, [config]); - return ( <> -
- -
-
- - -
-
-
{config ? "Select a file to read it." : "Loading…"}
-
-
+ {!config ? ( + } topbar={}> +
Loading…
+
+ ) : config.mode === "hub" ? ( + + ) : ( + + )} + + ); } diff --git a/internal/webapp/frontend/src/api/types.ts b/internal/webapp/frontend/src/api/types.ts index 2f624fe..4b546e9 100644 --- a/internal/webapp/frontend/src/api/types.ts +++ b/internal/webapp/frontend/src/api/types.ts @@ -30,3 +30,38 @@ export interface Project { export interface ProjectList { projects: Project[]; } + +// POST /api/projects (handleProjectCreate) — create-or-join by name. +export interface ProjectCreated { + project: Project; + created: boolean; +} + +// GET /api/orgs (handleOrgList, orgs.go) +export interface OrgMember { + email: string; + role: string; // "owner" | "member" +} + +export interface Org { + id: string; + name: string; + role: string; // the signed-in account's role in this org + members: OrgMember[]; + created?: string; +} + +export interface OrgList { + orgs: Org[]; +} + +// POST /api/invites/{token} (handleInviteAccept, orgs.go) +export interface InviteAccepted { + ok: boolean; + org: { id: string; name: string }; +} + +// GET /api/admin/pending (handleAdminPending, admin.go) +export interface PendingList { + pending: Array<{ id: string; email: string; name: string }>; +} diff --git a/internal/webapp/frontend/src/apps/HubApp.tsx b/internal/webapp/frontend/src/apps/HubApp.tsx new file mode 100644 index 0000000..1b4990a --- /dev/null +++ b/internal/webapp/frontend/src/apps/HubApp.tsx @@ -0,0 +1,168 @@ +import { useEffect, useMemo, useState } from "react"; +import { Navigate, useLocation, useNavigate } from "react-router-dom"; +import { postJSON } from "../api/http"; +import type { InviteAccepted, Project, ProjectCreated, ServerConfig } from "../api/types"; +import { useOrgs, usePending, useProjects, useHubRefresh } from "../hooks/useHub"; +import { parseRoute } from "../router"; +import { AppShell, Topbar, VaultHeader } from "../components/shell"; +import { ProjectNav } from "../components/ProjectNav"; +import { OrgBar } from "../components/OrgBar"; +import { EmptyState } from "../components/EmptyState"; +import { toast } from "../toast"; + +export default function HubApp({ config }: { config: ServerConfig }) { + const location = useLocation(); + const navigate = useNavigate(); + const refresh = useHubRefresh(); + // Org just joined via an invite this page-load: prefer its projects over + // whatever happens to be first in the list. + const [joinedOrgId, setJoinedOrgId] = useState(null); + + const joinToken = useMemo(() => { + const m = location.pathname.match(/^\/join\/([0-9a-f]+)\/?$/); + return m ? m[1] : null; + }, [location.pathname]); + + const { data: projects } = useProjects(!joinToken); + const { data: orgs } = useOrgs(!joinToken); + const isAdmin = !!config.auth.admin; + const { data: pending } = usePending(isAdmin); + + const route = useMemo( + () => parseRoute(location.pathname, "hub"), + [location.pathname], + ); + + const current: Project | null = useMemo(() => { + if (!projects) return null; + return ( + projects.find((p) => p.id === route.project) || + (joinedOrgId && projects.find((p) => p.org === joinedOrgId)) || + projects[0] || + null + ); + }, [projects, route.project, joinedOrgId]); + + useEffect(() => { + document.title = current + ? current.name + " — BearDrive" + : config.brand || config.volume || "BearDrive"; + }, [current, config]); + + if (joinToken) { + return ( + { + setJoinedOrgId(orgId); + await refresh(); + navigate("/", { replace: true }); + }} + /> + ); + } + + const brand = config.brand || config.volume || "BearDrive"; + const org = (current && orgs?.find((o) => o.id === current.org)) || null; + const ownedOrg = orgs?.find((o) => o.role === "owner") || null; + // The top-of-sidebar gear is the always-visible admin entry point: any + // account that owns an org (or is a hub admin) gets it, whatever project + // is open. The panels it opens arrive in Phase 4. + const gearTarget = org && org.role === "owner" ? org : ownedOrg; + + const vault = ( + navigate("/" + current.id) : undefined} + showSignout={config.auth.enabled} + admin={isAdmin ? { pending: pending?.length || 0, onClick: () => {} } : undefined} + gear={gearTarget ? { onClick: () => {} } : undefined} + /> + ); + + if (!projects || !orgs) { + return ( + }> +
Loading…
+
+ ); + } + + if (!current) { + return ( + } + topbar={} + contentClass="view" + > + { + if (!name) { + toast("Give the project a name.", true); + return; + } + try { + const out = await postJSON("/api/projects", { name }); + await refresh(); + navigate("/" + out.project.id); + toast(`Created “${out.project.name}”.`); + } catch (e) { + toast("Could not create the project: " + (e as Error).message, true); + } + }} + /> + + ); + } + + // Landing ("/") and unknown project ids both resolve to a real project + // URL; replace so back/forward never bounces through the redirect. + if (route.project !== current.id) { + return ; + } + + return ( + } + orgBar={ {}} />} + topbar={} + > + {/* Content views (project home, files, insights, history) arrive in + Phases 2–3. */} +
Select a file to read it.
+
+ ); +} + +/* Opening "/join/" joins the invite's org. If the visitor isn't + signed in yet, the 401 handler sends them to /auth/login with the /join + path intact in `next`, so after signing in the server re-serves the app + here and the join completes — the token is never lost. */ +function JoinInvite({ token, onDone }: { token: string; onDone: (orgId: string | null) => void }) { + useEffect(() => { + let cancelled = false; + postJSON("/api/invites/" + token) + .then((out) => { + if (cancelled) return; + toast(`Welcome — you joined the “${out.org.name}” team. Opening its projects…`); + onDone(out.org.id); + }) + .catch((e) => { + if (cancelled || String((e as Error).message).includes("signing in")) return; + toast("Could not accept the invite: " + (e as Error).message, true); + onDone(null); + }); + return () => { + cancelled = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + return ( + } topbar={}> +
Joining…
+
+ ); +} diff --git a/internal/webapp/frontend/src/apps/VolumeApp.tsx b/internal/webapp/frontend/src/apps/VolumeApp.tsx new file mode 100644 index 0000000..ff48c8c --- /dev/null +++ b/internal/webapp/frontend/src/apps/VolumeApp.tsx @@ -0,0 +1,21 @@ +import { useEffect } from "react"; +import type { ServerConfig } from "../api/types"; +import { AppShell, Topbar, VaultHeader } from "../components/shell"; + +// Single-volume mode: one folder, no projects or orgs. File browsing +// arrives in Phase 2. +export default function VolumeApp({ config }: { config: ServerConfig }) { + const name = config.volume || "BearDrive"; + useEffect(() => { + document.title = config.brand || name; + }, [config, name]); + + return ( + } + topbar={} + > +
Select a file to read it.
+
+ ); +} diff --git a/internal/webapp/frontend/src/components/EmptyState.tsx b/internal/webapp/frontend/src/components/EmptyState.tsx new file mode 100644 index 0000000..24e899e --- /dev/null +++ b/internal/webapp/frontend/src/components/EmptyState.tsx @@ -0,0 +1,55 @@ +import { useRef } from "react"; +import { toast } from "../toast"; + +// Onboarding: a signed-in account with no projects shouldn't hit a blank +// sidebar. Explain that access comes from an invite, let them paste one, +// and — since any member can — offer to start a new project. +export function EmptyState({ + authEnabled, + onCreate, +}: { + authEnabled: boolean; + onCreate: (name: string) => void; +}) { + const invite = useRef(null); + const name = useRef(null); + + const join = () => { + const v = invite.current!.value.trim(); + const m = v.match(/join\/([0-9a-f]+)/) || v.match(/^([0-9a-f]{8,})$/); + if (!m) { + toast("That doesn't look like an invite link.", true); + return; + } + location.href = "/join/" + m[1]; + }; + + return ( +
+

Welcome to BearDrive

+

You're signed in, but you're not part of any project yet.

+ {authEnabled && ( +
+

Have an invite link?

+

A teammate can send you a join link. Paste it here:

+
+ + +
+
+ )} +
+

Or start a new project

+

Create a shared space for your team's files.

+
+ + +
+
+
+ ); +} diff --git a/internal/webapp/frontend/src/components/OrgBar.tsx b/internal/webapp/frontend/src/components/OrgBar.tsx new file mode 100644 index 0000000..91bdfda --- /dev/null +++ b/internal/webapp/frontend/src/components/OrgBar.tsx @@ -0,0 +1,32 @@ +import type { Org } from "../api/types"; + +// The sidebar footer names the project's org; clicking it opens the org +// admin panel, and owners get a Manage button that does the same. The +// panel itself arrives with the admin surfaces (Phase 4). +export function OrgBar({ org, onManage }: { org: Org | null; onManage: (org: Org) => void }) { + if (!org) return null; + return ( +
+ onManage(org)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onManage(org); + } + }} + > + {org.name} + + {org.role === "owner" && ( + + )} +
+ ); +} diff --git a/internal/webapp/frontend/src/components/ProjectNav.tsx b/internal/webapp/frontend/src/components/ProjectNav.tsx new file mode 100644 index 0000000..6333b84 --- /dev/null +++ b/internal/webapp/frontend/src/components/ProjectNav.tsx @@ -0,0 +1,72 @@ +import { useNavigate } from "react-router-dom"; +import { postJSON } from "../api/http"; +import type { Project, ProjectCreated } from "../api/types"; +import { modalPrompt } from "../modal"; +import { toast } from "../toast"; +import { useHubRefresh } from "../hooks/useHub"; +import { closeSidebarOnMobile } from "./shell"; + +// Deterministic accent for a project's letter-mark, so each project keeps a +// stable color across reloads without any server state. +const PROJ_COLORS = ["#5b8def", "#f5a623", "#4cc38a", "#e0679b", "#8b7bf0", "#3ec8c8", "#e6934a"]; +export function projColor(s: string): string { + let h = 0; + for (const c of s) h = (h * 31 + c.charCodeAt(0)) >>> 0; + return PROJ_COLORS[h % PROJ_COLORS.length]; +} + +export function ProjectNav({ projects, currentId }: { projects: Project[]; currentId?: string }) { + const navigate = useNavigate(); + const refresh = useHubRefresh(); + + const create = async () => { + const name = await modalPrompt("New project", "Project name", "", "Create"); + if (!name) return; + try { + const out = await postJSON("/api/projects", { name }); + await refresh(); + navigate("/" + out.project.id); + toast(`Created “${out.project.name}”.`); + } catch (e) { + toast("Could not create the project: " + (e as Error).message, true); + } + }; + + return ( + + ); +} diff --git a/internal/webapp/frontend/src/components/shell.tsx b/internal/webapp/frontend/src/components/shell.tsx new file mode 100644 index 0000000..1c13ebd --- /dev/null +++ b/internal/webapp/frontend/src/components/shell.tsx @@ -0,0 +1,125 @@ +import type { ReactNode } from "react"; + +// The app's fixed layout: off-canvas sidebar (mobile: body.sb-open toggles +// it), topbar, and the content pane. Ids and classes match the classic app +// so style.css applies unchanged. + +export function toggleSidebar() { + document.body.classList.toggle("sb-open"); +} +export function closeSidebarOnMobile() { + document.body.classList.remove("sb-open"); +} + +export function Icon({ name }: { name: string }) { + return ( + + ); +} + +export function AppShell(props: { + vault: ReactNode; + projectsNav?: ReactNode; + tree?: ReactNode; + orgBar?: ReactNode; + topbar: ReactNode; + contentClass?: string; + children: ReactNode; +}) { + return ( + <> +
+ +
+ {props.topbar} +
+ {props.children} +
+
+ + ); +} + +export function VaultHeader(props: { + name: string; + onHome?: () => void; // hub: the project name doubles as a home link + showSignout: boolean; + admin?: { pending: number; onClick: () => void }; // hub admins only + gear?: { onClick: () => void }; // org owners: manage organization +}) { + const { name, onHome, showSignout, admin, gear } = props; + return ( +
+ + { + if (onHome && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + onHome(); + } + }} + > + {name} + +
+ {admin && ( + + )} + {gear && ( + + )} + {showSignout && ( + + + + )} +
+
+ ); +} + +export function Topbar(props: { crumb?: ReactNode; meta?: ReactNode; actions?: ReactNode }) { + return ( +
+ + {props.crumb} + {props.meta} + {props.actions} +
+ ); +} diff --git a/internal/webapp/frontend/src/hooks/useHub.ts b/internal/webapp/frontend/src/hooks/useHub.ts new file mode 100644 index 0000000..29f9c40 --- /dev/null +++ b/internal/webapp/frontend/src/hooks/useHub.ts @@ -0,0 +1,49 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { getJSON } from "../api/http"; +import type { OrgList, PendingList, ProjectList } from "../api/types"; + +// Hub-wide server state: the project list (polled — new projects appear +// without a reload, matching the classic app's 30s refresh) and the orgs +// the signed-in account belongs to. + +export function useProjects(enabled: boolean) { + return useQuery({ + queryKey: ["projects"], + queryFn: () => getJSON("/api/projects"), + enabled, + refetchInterval: 30_000, + select: (d) => d.projects || [], + }); +} + +export function useOrgs(enabled: boolean) { + return useQuery({ + queryKey: ["orgs"], + queryFn: () => getJSON("/api/orgs"), + enabled, + select: (d) => d.orgs || [], + }); +} + +// Pending signups; only fetched for hub admins (the admin bar shows the +// count). +export function usePending(enabled: boolean) { + return useQuery({ + queryKey: ["admin", "pending"], + queryFn: () => getJSON("/api/admin/pending"), + enabled, + select: (d) => d.pending || [], + }); +} + +// Resolves once the refetches land — await it before navigating to a +// just-created project, or the router's unknown-id fallback will bounce +// off the stale list. +export function useHubRefresh() { + const qc = useQueryClient(); + return () => + Promise.all([ + qc.invalidateQueries({ queryKey: ["projects"] }), + qc.invalidateQueries({ queryKey: ["orgs"] }), + ]).then(() => {}); +} diff --git a/internal/webapp/frontend/src/modal.tsx b/internal/webapp/frontend/src/modal.tsx new file mode 100644 index 0000000..e7cfc38 --- /dev/null +++ b/internal/webapp/frontend/src/modal.tsx @@ -0,0 +1,141 @@ +import { useEffect, useRef, useSyncExternalStore } from "react"; + +// In-app modal prompt/confirm replacing native prompt()/confirm(). +// Imperative promise-based API (awaitable from event handlers, like the +// classic app); (mounted once in App) renders the active one. + +type Prompt = { + kind: "prompt"; + title: string; + label: string; + value: string; + okLabel: string; + resolve: (v: string | null) => void; +}; +type Confirm = { + kind: "confirm"; + title: string; + message: string; + confirmLabel: string; + danger: boolean; + resolve: (v: boolean) => void; +}; +type Modal = Prompt | Confirm; + +let current: Modal | null = null; +let listeners: Array<() => void> = []; +function emit(next: Modal | null) { + current = next; + listeners.forEach((l) => l()); +} + +export function modalPrompt( + title: string, + label: string, + value = "", + okLabel = "OK", +): Promise { + return new Promise((resolve) => + emit({ kind: "prompt", title, label, value, okLabel, resolve }), + ); +} + +export function modalConfirm( + title: string, + message: string, + confirmLabel = "Confirm", + danger = false, +): Promise { + return new Promise((resolve) => + emit({ kind: "confirm", title, message, confirmLabel, danger, resolve }), + ); +} + +export function ModalHost() { + const m = useSyncExternalStore( + (l) => { + listeners.push(l); + return () => { + listeners = listeners.filter((x) => x !== l); + }; + }, + () => current, + ); + if (!m) return null; + return m.kind === "prompt" ? : ; +} + +function close() { + emit(null); +} + +function PromptModal({ m }: { m: Prompt }) { + const input = useRef(null); + const done = (v: string | null) => { + close(); + m.resolve(v); + }; + const ok = () => done(input.current!.value.trim() || null); + useEffect(() => { + input.current!.focus(); + input.current!.select(); + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") done(null); + if (e.key === "Enter") ok(); + }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( +
e.target === e.currentTarget && done(null)}> +
+

{m.title}

+ + +
+ + +
+
+
+ ); +} + +function ConfirmModal({ m }: { m: Confirm }) { + const okBtn = useRef(null); + const done = (v: boolean) => { + close(); + m.resolve(v); + }; + useEffect(() => { + okBtn.current!.focus(); + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") done(false); + if (e.key === "Enter") done(true); + }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( +
e.target === e.currentTarget && done(false)}> +
+

{m.title}

+

{m.message}

+
+ + +
+
+
+ ); +} diff --git a/internal/webapp/frontend/src/router.ts b/internal/webapp/frontend/src/router.ts new file mode 100644 index 0000000..1451ff1 --- /dev/null +++ b/internal/webapp/frontend/src/router.ts @@ -0,0 +1,64 @@ +// Native path routing (no hash, no %2F): +// volume mode: / +// hub mode: // +// invite: /join/ +// Each path segment is percent-encoded for odd characters, but the "/" +// separators stay literal so the URL reads like a real file path. This is +// why routes are parsed by hand instead of with a route-matching library: +// encoded slashes must survive. + +export function encodePath(p: string): string { + return p.split("/").map(encodeURIComponent).join("/"); +} +export function decodePath(p: string): string { + return p.split("/").map(decodeURIComponent).join("/"); +} + +// Special views are RESTful routes under the project — the first segment +// after the project id is reserved when it names a view: +// //insights the Insights dashboard +// //history[/] change feed (project / subtree / file) +// (Root-level files literally named "insights" or "history" lose the URL +// shortcut and remain reachable through the tree.) +export const VIEW_ROUTES = new Set(["insights", "history"]); + +export interface Route { + project?: string; + path: string; + view?: "insights" | "history"; + viewTarget?: string; +} + +export function parseRoute(pathname: string, mode: "volume" | "hub"): Route { + const raw = pathname.replace(/^\/+/, ""); + if (mode !== "hub") return { path: raw ? decodePath(raw) : "" }; + const slash = raw.indexOf("/"); + if (slash === -1) return { project: raw, path: "" }; + const r: Route = { project: raw.slice(0, slash), path: decodePath(raw.slice(slash + 1)) }; + const seg = r.path.indexOf("/"); + const head = seg === -1 ? r.path : r.path.slice(0, seg); + if (VIEW_ROUTES.has(head)) { + r.view = head as "insights" | "history"; + r.viewTarget = seg === -1 ? "" : r.path.slice(seg + 1).replace(/\/+$/, ""); + r.path = ""; + } + return r; +} + +// The URL for a file within a project (hub) or the volume (no project id). +export function urlForPath(path: string, projectId?: string): string { + const enc = encodePath(path); + if (projectId) return "/" + projectId + (enc ? "/" + enc : ""); + return "/" + enc; +} + +// The URL for a special view of a project. +export function urlForView( + view: "insights" | "history", + projectId?: string, + target?: string, +): string { + let s = (projectId ? "/" + projectId : "") + "/" + view; + if (view === "history" && target) s += "/" + encodePath(target.replace(/\/+$/, "")); + return s; +} diff --git a/internal/webapp/frontend/src/toast.tsx b/internal/webapp/frontend/src/toast.tsx new file mode 100644 index 0000000..e6f734a --- /dev/null +++ b/internal/webapp/frontend/src/toast.tsx @@ -0,0 +1,37 @@ +import { useSyncExternalStore } from "react"; + +// Transient toast, replacing blocking alert(). Imperative `toast()` from +// anywhere; (mounted once in App) renders it. + +type ToastState = { msg: string; err: boolean; shown: boolean }; +let state: ToastState = { msg: "", err: false, shown: false }; +let listeners: Array<() => void> = []; +let timer: ReturnType | undefined; + +function emit(next: ToastState) { + state = next; + listeners.forEach((l) => l()); +} + +export function toast(msg: string, isErr = false) { + emit({ msg, err: isErr, shown: true }); + clearTimeout(timer); + timer = setTimeout(() => emit({ ...state, shown: false }), 3200); +} + +export function Toaster() { + const s = useSyncExternalStore( + (l) => { + listeners.push(l); + return () => { + listeners = listeners.filter((x) => x !== l); + }; + }, + () => state, + ); + return ( +
+ {s.msg} +
+ ); +} diff --git a/internal/webapp/static/assets/index-BoqNtp9Y.js b/internal/webapp/static/assets/index-BoqNtp9Y.js new file mode 100644 index 0000000..de4f3a3 --- /dev/null +++ b/internal/webapp/static/assets/index-BoqNtp9Y.js @@ -0,0 +1,11 @@ +(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const d of o)if(d.type==="childList")for(const m of d.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&r(m)}).observe(document,{childList:!0,subtree:!0});function s(o){const d={};return o.integrity&&(d.integrity=o.integrity),o.referrerPolicy&&(d.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?d.credentials="include":o.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function r(o){if(o.ep)return;o.ep=!0;const d=s(o);fetch(o.href,d)}})();var Fs={exports:{}},Bn={};var _d;function h0(){if(_d)return Bn;_d=1;var n=Symbol.for("react.transitional.element"),c=Symbol.for("react.fragment");function s(r,o,d){var m=null;if(d!==void 0&&(m=""+d),o.key!==void 0&&(m=""+o.key),"key"in o){d={};for(var b in o)b!=="key"&&(d[b]=o[b])}else d=o;return o=d.ref,{$$typeof:n,type:r,key:m,ref:o!==void 0?o:null,props:d}}return Bn.Fragment=c,Bn.jsx=s,Bn.jsxs=s,Bn}var Dd;function d0(){return Dd||(Dd=1,Fs.exports=h0()),Fs.exports}var H=d0(),ks={exports:{}},I={};var xd;function m0(){if(xd)return I;xd=1;var n=Symbol.for("react.transitional.element"),c=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),d=Symbol.for("react.consumer"),m=Symbol.for("react.context"),b=Symbol.for("react.forward_ref"),y=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),O=Symbol.for("react.activity"),_=Symbol.iterator;function L(S){return S===null||typeof S!="object"?null:(S=_&&S[_]||S["@@iterator"],typeof S=="function"?S:null)}var Q={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,q={};function X(S,N,w){this.props=S,this.context=N,this.refs=q,this.updater=w||Q}X.prototype.isReactComponent={},X.prototype.setState=function(S,N){if(typeof S!="object"&&typeof S!="function"&&S!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,S,N,"setState")},X.prototype.forceUpdate=function(S){this.updater.enqueueForceUpdate(this,S,"forceUpdate")};function G(){}G.prototype=X.prototype;function V(S,N,w){this.props=S,this.context=N,this.refs=q,this.updater=w||Q}var ct=V.prototype=new G;ct.constructor=V,B(ct,X.prototype),ct.isPureReactComponent=!0;var st=Array.isArray;function Et(){}var k={H:null,A:null,T:null,S:null},rt=Object.prototype.hasOwnProperty;function zt(S,N,w){var K=w.ref;return{$$typeof:n,type:S,key:N,ref:K!==void 0?K:null,props:w}}function $t(S,N){return zt(S.type,N,S.props)}function Wt(S){return typeof S=="object"&&S!==null&&S.$$typeof===n}function Dt(S){var N={"=":"=0",":":"=2"};return"$"+S.replace(/[=:]/g,function(w){return N[w]})}var It=/\/+/g;function Ee(S,N){return typeof S=="object"&&S!==null&&S.key!=null?Dt(""+S.key):N.toString(36)}function Nt(S){switch(S.status){case"fulfilled":return S.value;case"rejected":throw S.reason;default:switch(typeof S.status=="string"?S.then(Et,Et):(S.status="pending",S.then(function(N){S.status==="pending"&&(S.status="fulfilled",S.value=N)},function(N){S.status==="pending"&&(S.status="rejected",S.reason=N)})),S.status){case"fulfilled":return S.value;case"rejected":throw S.reason}}throw S}function x(S,N,w,K,P){var lt=typeof S;(lt==="undefined"||lt==="boolean")&&(S=null);var mt=!1;if(S===null)mt=!0;else switch(lt){case"bigint":case"string":case"number":mt=!0;break;case"object":switch(S.$$typeof){case n:case c:mt=!0;break;case T:return mt=S._init,x(mt(S._payload),N,w,K,P)}}if(mt)return P=P(S),mt=K===""?"."+Ee(S,0):K,st(P)?(w="",mt!=null&&(w=mt.replace(It,"$&/")+"/"),x(P,N,w,"",function(Xa){return Xa})):P!=null&&(Wt(P)&&(P=$t(P,w+(P.key==null||S&&S.key===P.key?"":(""+P.key).replace(It,"$&/")+"/")+mt)),N.push(P)),1;mt=0;var Pt=K===""?".":K+":";if(st(S))for(var xt=0;xt>>1,Tt=x[pt];if(0>>1;pto(w,W))Ko(P,w)?(x[pt]=P,x[K]=W,pt=K):(x[pt]=w,x[N]=W,pt=N);else if(Ko(P,W))x[pt]=P,x[K]=W,pt=K;else break t}}return Y}function o(x,Y){var W=x.sortIndex-Y.sortIndex;return W!==0?W:x.id-Y.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var d=performance;n.unstable_now=function(){return d.now()}}else{var m=Date,b=m.now();n.unstable_now=function(){return m.now()-b}}var y=[],p=[],T=1,O=null,_=3,L=!1,Q=!1,B=!1,q=!1,X=typeof setTimeout=="function"?setTimeout:null,G=typeof clearTimeout=="function"?clearTimeout:null,V=typeof setImmediate<"u"?setImmediate:null;function ct(x){for(var Y=s(p);Y!==null;){if(Y.callback===null)r(p);else if(Y.startTime<=x)r(p),Y.sortIndex=Y.expirationTime,c(y,Y);else break;Y=s(p)}}function st(x){if(B=!1,ct(x),!Q)if(s(y)!==null)Q=!0,Et||(Et=!0,Dt());else{var Y=s(p);Y!==null&&Nt(st,Y.startTime-x)}}var Et=!1,k=-1,rt=5,zt=-1;function $t(){return q?!0:!(n.unstable_now()-ztx&&$t());){var pt=O.callback;if(typeof pt=="function"){O.callback=null,_=O.priorityLevel;var Tt=pt(O.expirationTime<=x);if(x=n.unstable_now(),typeof Tt=="function"){O.callback=Tt,ct(x),Y=!0;break e}O===s(y)&&r(y),ct(x)}else r(y);O=s(y)}if(O!==null)Y=!0;else{var S=s(p);S!==null&&Nt(st,S.startTime-x),Y=!1}}break t}finally{O=null,_=W,L=!1}Y=void 0}}finally{Y?Dt():Et=!1}}}var Dt;if(typeof V=="function")Dt=function(){V(Wt)};else if(typeof MessageChannel<"u"){var It=new MessageChannel,Ee=It.port2;It.port1.onmessage=Wt,Dt=function(){Ee.postMessage(null)}}else Dt=function(){X(Wt,0)};function Nt(x,Y){k=X(function(){x(n.unstable_now())},Y)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(x){x.callback=null},n.unstable_forceFrameRate=function(x){0>x||125pt?(x.sortIndex=W,c(p,x),s(y)===null&&x===s(p)&&(B?(G(k),k=-1):B=!0,Nt(st,W-pt))):(x.sortIndex=Tt,c(y,x),Q||L||(Q=!0,Et||(Et=!0,Dt()))),x},n.unstable_shouldYield=$t,n.unstable_wrapCallback=function(x){var Y=_;return function(){var W=_;_=Y;try{return x.apply(this,arguments)}finally{_=W}}}})(Is)),Is}var Nd;function v0(){return Nd||(Nd=1,Ws.exports=y0()),Ws.exports}var Ps={exports:{}},kt={};var Hd;function p0(){if(Hd)return kt;Hd=1;var n=hf();function c(y){var p="https://react.dev/errors/"+y;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(c){console.error(c)}}return n(),Ps.exports=p0(),Ps.exports}var Bd;function S0(){if(Bd)return Qn;Bd=1;var n=v0(),c=hf(),s=g0();function r(t){var e="https://react.dev/errors/"+t;if(1Tt||(t.current=pt[Tt],pt[Tt]=null,Tt--)}function w(t,e){Tt++,pt[Tt]=t.current,t.current=e}var K=S(null),P=S(null),lt=S(null),mt=S(null);function Pt(t,e){switch(w(lt,e),w(P,t),w(K,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Ih(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Ih(e),t=Ph(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}N(K),w(K,t)}function xt(){N(K),N(P),N(lt)}function Xa(t){t.memoizedState!==null&&w(mt,t);var e=K.current,l=Ph(e,t.type);e!==l&&(w(P,t),w(K,l))}function Jn(t){P.current===t&&(N(K),N(P)),mt.current===t&&(N(mt),jn._currentValue=W)}var Di,zf;function Hl(t){if(Di===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);Di=e&&e[1]||"",zf=-1)":-1u||v[a]!==A[u]){var D=` +`+v[a].replace(" at new "," at ");return t.displayName&&D.includes("")&&(D=D.replace("",t.displayName)),D}while(1<=a&&0<=u);break}}}finally{xi=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Hl(l):""}function Xm(t,e){switch(t.tag){case 26:case 27:case 5:return Hl(t.type);case 16:return Hl("Lazy");case 13:return t.child!==e&&e!==null?Hl("Suspense Fallback"):Hl("Suspense");case 19:return Hl("SuspenseList");case 0:case 15:return Ui(t.type,!1);case 11:return Ui(t.type.render,!1);case 1:return Ui(t.type,!0);case 31:return Hl("Activity");default:return""}}function Mf(t){try{var e="",l=null;do e+=Xm(t,l),l=t,t=t.return;while(t);return e}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var ji=Object.prototype.hasOwnProperty,Ni=n.unstable_scheduleCallback,Hi=n.unstable_cancelCallback,Zm=n.unstable_shouldYield,Vm=n.unstable_requestPaint,fe=n.unstable_now,Km=n.unstable_getCurrentPriorityLevel,_f=n.unstable_ImmediatePriority,Df=n.unstable_UserBlockingPriority,Fn=n.unstable_NormalPriority,Jm=n.unstable_LowPriority,xf=n.unstable_IdlePriority,Fm=n.log,km=n.unstable_setDisableYieldValue,Za=null,re=null;function sl(t){if(typeof Fm=="function"&&km(t),re&&typeof re.setStrictMode=="function")try{re.setStrictMode(Za,t)}catch{}}var oe=Math.clz32?Math.clz32:Im,$m=Math.log,Wm=Math.LN2;function Im(t){return t>>>=0,t===0?32:31-($m(t)/Wm|0)|0}var kn=256,$n=262144,Wn=4194304;function ql(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function In(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var u=0,i=t.suspendedLanes,f=t.pingedLanes;t=t.warmLanes;var h=a&134217727;return h!==0?(a=h&~i,a!==0?u=ql(a):(f&=h,f!==0?u=ql(f):l||(l=h&~t,l!==0&&(u=ql(l))))):(h=a&~i,h!==0?u=ql(h):f!==0?u=ql(f):l||(l=a&~t,l!==0&&(u=ql(l)))),u===0?0:e!==0&&e!==u&&(e&i)===0&&(i=u&-u,l=e&-e,i>=l||i===32&&(l&4194048)!==0)?e:u}function Va(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function Pm(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Uf(){var t=Wn;return Wn<<=1,(Wn&62914560)===0&&(Wn=4194304),t}function qi(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Ka(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ty(t,e,l,a,u,i){var f=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var h=t.entanglements,v=t.expirationTimes,A=t.hiddenUpdates;for(l=f&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var iy=/[\n"\\]/g;function Oe(t){return t.replace(iy,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function Gi(t,e,l,a,u,i,f,h){t.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?t.type=f:t.removeAttribute("type"),e!=null?f==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Te(e)):t.value!==""+Te(e)&&(t.value=""+Te(e)):f!=="submit"&&f!=="reset"||t.removeAttribute("value"),e!=null?Xi(t,f,Te(e)):l!=null?Xi(t,f,Te(l)):a!=null&&t.removeAttribute("value"),u==null&&i!=null&&(t.defaultChecked=!!i),u!=null&&(t.checked=u&&typeof u!="function"&&typeof u!="symbol"),h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"?t.name=""+Te(h):t.removeAttribute("name")}function Vf(t,e,l,a,u,i,f,h){if(i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(t.type=i),e!=null||l!=null){if(!(i!=="submit"&&i!=="reset"||e!=null)){wi(t);return}l=l!=null?""+Te(l):"",e=e!=null?""+Te(e):l,h||e===t.value||(t.value=e),t.defaultValue=e}a=a??u,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=h?t.checked:!!a,t.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(t.name=f),wi(t)}function Xi(t,e,l){e==="number"&&eu(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function ia(t,e,l,a){if(t=t.options,e){e={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Fi=!1;if(Ve)try{var $a={};Object.defineProperty($a,"passive",{get:function(){Fi=!0}}),window.addEventListener("test",$a,$a),window.removeEventListener("test",$a,$a)}catch{Fi=!1}var rl=null,ki=null,au=null;function If(){if(au)return au;var t,e=ki,l=e.length,a,u="value"in rl?rl.value:rl.textContent,i=u.length;for(t=0;t=Pa),nr=" ",ur=!1;function ir(t,e){switch(t){case"keyup":return Ny.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function cr(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var ra=!1;function qy(t,e){switch(t){case"compositionend":return cr(e);case"keypress":return e.which!==32?null:(ur=!0,nr);case"textInput":return t=e.data,t===nr&&ur?null:t;default:return null}}function By(t,e){if(ra)return t==="compositionend"||!tc&&ir(t,e)?(t=If(),au=ki=rl=null,ra=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=yr(l)}}function pr(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?pr(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function gr(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=eu(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=eu(t.document)}return e}function ac(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var Vy=Ve&&"documentMode"in document&&11>=document.documentMode,oa=null,nc=null,an=null,uc=!1;function Sr(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;uc||oa==null||oa!==eu(a)||(a=oa,"selectionStart"in a&&ac(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),an&&ln(an,a)||(an=a,a=$u(nc,"onSelect"),0>=f,u-=f,Qe=1<<32-oe(e)+u|l<et?(it=J,J=null):it=J.sibling;var ht=C(E,J,R[et],U);if(ht===null){J===null&&(J=it);break}t&&J&&ht.alternate===null&&e(E,J),g=i(ht,g,et),ot===null?F=ht:ot.sibling=ht,ot=ht,J=it}if(et===R.length)return l(E,J),ft&&Je(E,et),F;if(J===null){for(;etet?(it=J,J=null):it=J.sibling;var Ul=C(E,J,ht.value,U);if(Ul===null){J===null&&(J=it);break}t&&J&&Ul.alternate===null&&e(E,J),g=i(Ul,g,et),ot===null?F=Ul:ot.sibling=Ul,ot=Ul,J=it}if(ht.done)return l(E,J),ft&&Je(E,et),F;if(J===null){for(;!ht.done;et++,ht=R.next())ht=j(E,ht.value,U),ht!==null&&(g=i(ht,g,et),ot===null?F=ht:ot.sibling=ht,ot=ht);return ft&&Je(E,et),F}for(J=a(J);!ht.done;et++,ht=R.next())ht=M(J,E,et,ht.value,U),ht!==null&&(t&&ht.alternate!==null&&J.delete(ht.key===null?et:ht.key),g=i(ht,g,et),ot===null?F=ht:ot.sibling=ht,ot=ht);return t&&J.forEach(function(o0){return e(E,o0)}),ft&&Je(E,et),F}function bt(E,g,R,U){if(typeof R=="object"&&R!==null&&R.type===B&&R.key===null&&(R=R.props.children),typeof R=="object"&&R!==null){switch(R.$$typeof){case L:t:{for(var F=R.key;g!==null;){if(g.key===F){if(F=R.type,F===B){if(g.tag===7){l(E,g.sibling),U=u(g,R.props.children),U.return=E,E=U;break t}}else if(g.elementType===F||typeof F=="object"&&F!==null&&F.$$typeof===rt&&Jl(F)===g.type){l(E,g.sibling),U=u(g,R.props),rn(U,R),U.return=E,E=U;break t}l(E,g);break}else e(E,g);g=g.sibling}R.type===B?(U=Gl(R.props.children,E.mode,U,R.key),U.return=E,E=U):(U=du(R.type,R.key,R.props,null,E.mode,U),rn(U,R),U.return=E,E=U)}return f(E);case Q:t:{for(F=R.key;g!==null;){if(g.key===F)if(g.tag===4&&g.stateNode.containerInfo===R.containerInfo&&g.stateNode.implementation===R.implementation){l(E,g.sibling),U=u(g,R.children||[]),U.return=E,E=U;break t}else{l(E,g);break}else e(E,g);g=g.sibling}U=hc(R,E.mode,U),U.return=E,E=U}return f(E);case rt:return R=Jl(R),bt(E,g,R,U)}if(Nt(R))return Z(E,g,R,U);if(Dt(R)){if(F=Dt(R),typeof F!="function")throw Error(r(150));return R=F.call(R),$(E,g,R,U)}if(typeof R.then=="function")return bt(E,g,bu(R),U);if(R.$$typeof===V)return bt(E,g,vu(E,R),U);Eu(E,R)}return typeof R=="string"&&R!==""||typeof R=="number"||typeof R=="bigint"?(R=""+R,g!==null&&g.tag===6?(l(E,g.sibling),U=u(g,R),U.return=E,E=U):(l(E,g),U=oc(R,E.mode,U),U.return=E,E=U),f(E)):l(E,g)}return function(E,g,R,U){try{fn=0;var F=bt(E,g,R,U);return Ta=null,F}catch(J){if(J===Ea||J===gu)throw J;var ot=de(29,J,null,E.mode);return ot.lanes=U,ot.return=E,ot}}}var kl=Gr(!0),Xr=Gr(!1),yl=!1;function Rc(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ac(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function vl(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function pl(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(dt&2)!==0){var u=a.pending;return u===null?e.next=e:(e.next=u.next,u.next=e),a.pending=e,e=hu(t),Cr(t,null,l),e}return ou(t,a,e,l),hu(t)}function on(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Nf(t,l)}}function Cc(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var u=null,i=null;if(l=l.firstBaseUpdate,l!==null){do{var f={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};i===null?u=i=f:i=i.next=f,l=l.next}while(l!==null);i===null?u=i=e:i=i.next=e}else u=i=e;l={baseState:a.baseState,firstBaseUpdate:u,lastBaseUpdate:i,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var zc=!1;function hn(){if(zc){var t=ba;if(t!==null)throw t}}function dn(t,e,l,a){zc=!1;var u=t.updateQueue;yl=!1;var i=u.firstBaseUpdate,f=u.lastBaseUpdate,h=u.shared.pending;if(h!==null){u.shared.pending=null;var v=h,A=v.next;v.next=null,f===null?i=A:f.next=A,f=v;var D=t.alternate;D!==null&&(D=D.updateQueue,h=D.lastBaseUpdate,h!==f&&(h===null?D.firstBaseUpdate=A:h.next=A,D.lastBaseUpdate=v))}if(i!==null){var j=u.baseState;f=0,D=A=v=null,h=i;do{var C=h.lane&-536870913,M=C!==h.lane;if(M?(ut&C)===C:(a&C)===C){C!==0&&C===Sa&&(zc=!0),D!==null&&(D=D.next={lane:0,tag:h.tag,payload:h.payload,callback:null,next:null});t:{var Z=t,$=h;C=e;var bt=l;switch($.tag){case 1:if(Z=$.payload,typeof Z=="function"){j=Z.call(bt,j,C);break t}j=Z;break t;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=$.payload,C=typeof Z=="function"?Z.call(bt,j,C):Z,C==null)break t;j=O({},j,C);break t;case 2:yl=!0}}C=h.callback,C!==null&&(t.flags|=64,M&&(t.flags|=8192),M=u.callbacks,M===null?u.callbacks=[C]:M.push(C))}else M={lane:C,tag:h.tag,payload:h.payload,callback:h.callback,next:null},D===null?(A=D=M,v=j):D=D.next=M,f|=C;if(h=h.next,h===null){if(h=u.shared.pending,h===null)break;M=h,h=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);D===null&&(v=j),u.baseState=v,u.firstBaseUpdate=A,u.lastBaseUpdate=D,i===null&&(u.shared.lanes=0),Tl|=f,t.lanes=f,t.memoizedState=j}}function Zr(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function Vr(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;ti?i:8;var f=x.T,h={};x.T=h,Kc(t,!1,e,l);try{var v=u(),A=x.S;if(A!==null&&A(h,v),v!==null&&typeof v=="object"&&typeof v.then=="function"){var D=tv(v,a);vn(t,e,D,ge(t))}else vn(t,e,a,ge(t))}catch(j){vn(t,e,{then:function(){},status:"rejected",reason:j},ge())}finally{Y.p=i,f!==null&&h.types!==null&&(f.types=h.types),x.T=f}}function iv(){}function Zc(t,e,l,a){if(t.tag!==5)throw Error(r(476));var u=Ro(t).queue;Oo(t,u,e,W,l===null?iv:function(){return Ao(t),l(a)})}function Ro(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:We,lastRenderedState:W},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:We,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function Ao(t){var e=Ro(t);e.next===null&&(e=t.alternate.memoizedState),vn(t,e.next.queue,{},ge())}function Vc(){return Vt(jn)}function Co(){return jt().memoizedState}function zo(){return jt().memoizedState}function cv(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=ge();t=vl(l);var a=pl(e,t,l);a!==null&&(ce(a,e,l),on(a,e,l)),e={cache:bc()},t.payload=e;return}e=e.return}}function sv(t,e,l){var a=ge();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},xu(t)?_o(e,l):(l=fc(t,e,l,a),l!==null&&(ce(l,t,a),Do(l,e,a)))}function Mo(t,e,l){var a=ge();vn(t,e,l,a)}function vn(t,e,l,a){var u={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(xu(t))_o(e,u);else{var i=t.alternate;if(t.lanes===0&&(i===null||i.lanes===0)&&(i=e.lastRenderedReducer,i!==null))try{var f=e.lastRenderedState,h=i(f,l);if(u.hasEagerState=!0,u.eagerState=h,he(h,f))return ou(t,e,u,0),Ot===null&&ru(),!1}catch{}if(l=fc(t,e,u,a),l!==null)return ce(l,t,a),Do(l,e,a),!0}return!1}function Kc(t,e,l,a){if(a={lane:2,revertLane:Rs(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},xu(t)){if(e)throw Error(r(479))}else e=fc(t,l,a,2),e!==null&&ce(e,t,2)}function xu(t){var e=t.alternate;return t===tt||e!==null&&e===tt}function _o(t,e){Ra=Ru=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function Do(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Nf(t,l)}}var pn={readContext:Vt,use:zu,useCallback:Mt,useContext:Mt,useEffect:Mt,useImperativeHandle:Mt,useLayoutEffect:Mt,useInsertionEffect:Mt,useMemo:Mt,useReducer:Mt,useRef:Mt,useState:Mt,useDebugValue:Mt,useDeferredValue:Mt,useTransition:Mt,useSyncExternalStore:Mt,useId:Mt,useHostTransitionStatus:Mt,useFormState:Mt,useActionState:Mt,useOptimistic:Mt,useMemoCache:Mt,useCacheRefresh:Mt};pn.useEffectEvent=Mt;var xo={readContext:Vt,use:zu,useCallback:function(t,e){return te().memoizedState=[t,e===void 0?null:e],t},useContext:Vt,useEffect:mo,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,_u(4194308,4,go.bind(null,e,t),l)},useLayoutEffect:function(t,e){return _u(4194308,4,t,e)},useInsertionEffect:function(t,e){_u(4,2,t,e)},useMemo:function(t,e){var l=te();e=e===void 0?null:e;var a=t();if($l){sl(!0);try{t()}finally{sl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=te();if(l!==void 0){var u=l(e);if($l){sl(!0);try{l(e)}finally{sl(!1)}}}else u=e;return a.memoizedState=a.baseState=u,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:u},a.queue=t,t=t.dispatch=sv.bind(null,tt,t),[a.memoizedState,t]},useRef:function(t){var e=te();return t={current:t},e.memoizedState=t},useState:function(t){t=Lc(t);var e=t.queue,l=Mo.bind(null,tt,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:Gc,useDeferredValue:function(t,e){var l=te();return Xc(l,t,e)},useTransition:function(){var t=Lc(!1);return t=Oo.bind(null,tt,t.queue,!0,!1),te().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=tt,u=te();if(ft){if(l===void 0)throw Error(r(407));l=l()}else{if(l=e(),Ot===null)throw Error(r(349));(ut&127)!==0||Wr(a,e,l)}u.memoizedState=l;var i={value:l,getSnapshot:e};return u.queue=i,mo(Pr.bind(null,a,i,t),[t]),a.flags|=2048,Ca(9,{destroy:void 0},Ir.bind(null,a,i,l,e),null),l},useId:function(){var t=te(),e=Ot.identifierPrefix;if(ft){var l=Le,a=Qe;l=(a&~(1<<32-oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=Au++,0<\/script>",i=i.removeChild(i.firstChild);break;case"select":i=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?i.multiple=!0:a.size&&(i.size=a.size);break;default:i=typeof a.is=="string"?f.createElement(u,{is:a.is}):f.createElement(u)}}i[Xt]=e,i[ee]=a;t:for(f=e.child;f!==null;){if(f.tag===5||f.tag===6)i.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===e)break t;for(;f.sibling===null;){if(f.return===null||f.return===e)break t;f=f.return}f.sibling.return=f.return,f=f.sibling}e.stateNode=i;t:switch(Jt(i,u,a),u){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&Pe(e)}}return At(e),is(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&Pe(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(r(166));if(t=lt.current,pa(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,u=Zt,u!==null)switch(u.tag){case 27:case 5:a=u.memoizedProps}t[Xt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||$h(t.nodeValue,l)),t||dl(e,!0)}else t=Wu(t).createTextNode(a),t[Xt]=e,e.stateNode=t}return At(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=pa(e),l!==null){if(t===null){if(!a)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Xt]=e}else Xl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),t=!1}else l=vc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ye(e),e):(ye(e),null);if((e.flags&128)!==0)throw Error(r(558))}return At(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(u=pa(e),a!==null&&a.dehydrated!==null){if(t===null){if(!u)throw Error(r(318));if(u=e.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(r(317));u[Xt]=e}else Xl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),u=!1}else u=vc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=u),u=!0;if(!u)return e.flags&256?(ye(e),e):(ye(e),null)}return ye(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,u=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(u=a.alternate.memoizedState.cachePool.pool),i=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(i=a.memoizedState.cachePool.pool),i!==u&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),qu(e,e.updateQueue),At(e),null);case 4:return xt(),t===null&&Ms(e.stateNode.containerInfo),At(e),null;case 10:return ke(e.type),At(e),null;case 19:if(N(Ut),a=e.memoizedState,a===null)return At(e),null;if(u=(e.flags&128)!==0,i=a.rendering,i===null)if(u)Sn(a,!1);else{if(_t!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(i=Ou(t),i!==null){for(e.flags|=128,Sn(a,!1),t=i.updateQueue,e.updateQueue=t,qu(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)zr(l,t),l=l.sibling;return w(Ut,Ut.current&1|2),ft&&Je(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&fe()>wu&&(e.flags|=128,u=!0,Sn(a,!1),e.lanes=4194304)}else{if(!u)if(t=Ou(i),t!==null){if(e.flags|=128,u=!0,t=t.updateQueue,e.updateQueue=t,qu(e,t),Sn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!i.alternate&&!ft)return At(e),null}else 2*fe()-a.renderingStartTime>wu&&l!==536870912&&(e.flags|=128,u=!0,Sn(a,!1),e.lanes=4194304);a.isBackwards?(i.sibling=e.child,e.child=i):(t=a.last,t!==null?t.sibling=i:e.child=i,a.last=i)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=fe(),t.sibling=null,l=Ut.current,w(Ut,u?l&1|2:l&1),ft&&Je(e,a.treeForkCount),t):(At(e),null);case 22:case 23:return ye(e),_c(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(At(e),e.subtreeFlags&6&&(e.flags|=8192)):At(e),l=e.updateQueue,l!==null&&qu(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&N(Kl),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),ke(Ht),At(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function dv(t,e){switch(mc(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return ke(Ht),xt(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return Jn(e),null;case 31:if(e.memoizedState!==null){if(ye(e),e.alternate===null)throw Error(r(340));Xl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ye(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));Xl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return N(Ut),null;case 4:return xt(),null;case 10:return ke(e.type),null;case 22:case 23:return ye(e),_c(),t!==null&&N(Kl),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return ke(Ht),null;case 25:return null;default:return null}}function eh(t,e){switch(mc(e),e.tag){case 3:ke(Ht),xt();break;case 26:case 27:case 5:Jn(e);break;case 4:xt();break;case 31:e.memoizedState!==null&&ye(e);break;case 13:ye(e);break;case 19:N(Ut);break;case 10:ke(e.type);break;case 22:case 23:ye(e),_c(),t!==null&&N(Kl);break;case 24:ke(Ht)}}function bn(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var u=a.next;l=u;do{if((l.tag&t)===t){a=void 0;var i=l.create,f=l.inst;a=i(),f.destroy=a}l=l.next}while(l!==u)}}catch(h){vt(e,e.return,h)}}function bl(t,e,l){try{var a=e.updateQueue,u=a!==null?a.lastEffect:null;if(u!==null){var i=u.next;a=i;do{if((a.tag&t)===t){var f=a.inst,h=f.destroy;if(h!==void 0){f.destroy=void 0,u=e;var v=l,A=h;try{A()}catch(D){vt(u,v,D)}}}a=a.next}while(a!==i)}}catch(D){vt(e,e.return,D)}}function lh(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{Vr(e,l)}catch(a){vt(t,t.return,a)}}}function ah(t,e,l){l.props=Wl(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){vt(t,e,a)}}function En(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(u){vt(t,e,u)}}function Ye(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(u){vt(t,e,u)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(u){vt(t,e,u)}else l.current=null}function nh(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(u){vt(t,t.return,u)}}function cs(t,e,l){try{var a=t.stateNode;Hv(a,t.type,l,e),a[ee]=e}catch(u){vt(t,t.return,u)}}function uh(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&zl(t.type)||t.tag===4}function ss(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||uh(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&zl(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function fs(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=Ze));else if(a!==4&&(a===27&&zl(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(fs(t,e,l),t=t.sibling;t!==null;)fs(t,e,l),t=t.sibling}function Bu(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&zl(t.type)&&(l=t.stateNode),t=t.child,t!==null))for(Bu(t,e,l),t=t.sibling;t!==null;)Bu(t,e,l),t=t.sibling}function ih(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,u=e.attributes;u.length;)e.removeAttributeNode(u[0]);Jt(e,a,l),e[Xt]=t,e[ee]=l}catch(i){vt(t,t.return,i)}}var tl=!1,Qt=!1,rs=!1,ch=typeof WeakSet=="function"?WeakSet:Set,Gt=null;function mv(t,e){if(t=t.containerInfo,xs=ni,t=gr(t),ac(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var u=a.anchorOffset,i=a.focusNode;a=a.focusOffset;try{l.nodeType,i.nodeType}catch{l=null;break t}var f=0,h=-1,v=-1,A=0,D=0,j=t,C=null;e:for(;;){for(var M;j!==l||u!==0&&j.nodeType!==3||(h=f+u),j!==i||a!==0&&j.nodeType!==3||(v=f+a),j.nodeType===3&&(f+=j.nodeValue.length),(M=j.firstChild)!==null;)C=j,j=M;for(;;){if(j===t)break e;if(C===l&&++A===u&&(h=f),C===i&&++D===a&&(v=f),(M=j.nextSibling)!==null)break;j=C,C=j.parentNode}j=M}l=h===-1||v===-1?null:{start:h,end:v}}else l=null}l=l||{start:0,end:0}}else l=null;for(Us={focusedElem:t,selectionRange:l},ni=!1,Gt=e;Gt!==null;)if(e=Gt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Gt=t;else for(;Gt!==null;){switch(e=Gt,i=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),Jt(i,a,l),i[Xt]=t,wt(i),a=i;break t;case"link":var f=dd("link","href",u).get(a+(l.href||""));if(f){for(var h=0;hbt&&(f=bt,bt=$,$=f);var E=vr(h,$),g=vr(h,bt);if(E&&g&&(M.rangeCount!==1||M.anchorNode!==E.node||M.anchorOffset!==E.offset||M.focusNode!==g.node||M.focusOffset!==g.offset)){var R=j.createRange();R.setStart(E.node,E.offset),M.removeAllRanges(),$>bt?(M.addRange(R),M.extend(g.node,g.offset)):(R.setEnd(g.node,g.offset),M.addRange(R))}}}}for(j=[],M=h;M=M.parentNode;)M.nodeType===1&&j.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof h.focus=="function"&&h.focus(),h=0;hl?32:l,x.T=null,l=ps,ps=null;var i=Rl,f=ul;if(Yt=0,xa=Rl=null,ul=0,(dt&6)!==0)throw Error(r(331));var h=dt;if(dt|=4,gh(i.current),yh(i,i.current,f,l),dt=h,zn(0,!1),re&&typeof re.onPostCommitFiberRoot=="function")try{re.onPostCommitFiberRoot(Za,i)}catch{}return!0}finally{Y.p=u,x.T=a,qh(t,e)}}function Qh(t,e,l){e=Ae(l,e),e=$c(t.stateNode,e,2),t=pl(t,e,2),t!==null&&(Ka(t,2),we(t))}function vt(t,e,l){if(t.tag===3)Qh(t,t,l);else for(;e!==null;){if(e.tag===3){Qh(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Ol===null||!Ol.has(a))){t=Ae(l,t),l=Lo(2),a=pl(e,l,2),a!==null&&(Yo(l,a,e,t),Ka(a,2),we(a));break}}e=e.return}}function Es(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new pv;var u=new Set;a.set(e,u)}else u=a.get(e),u===void 0&&(u=new Set,a.set(e,u));u.has(l)||(ds=!0,u.add(l),t=Tv.bind(null,t,e,l),e.then(t,t))}function Tv(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,Ot===t&&(ut&l)===l&&(_t===4||_t===3&&(ut&62914560)===ut&&300>fe()-Yu?(dt&2)===0&&Ua(t,0):ms|=l,Da===ut&&(Da=0)),we(t)}function Lh(t,e){e===0&&(e=Uf()),t=wl(t,e),t!==null&&(Ka(t,e),we(t))}function Ov(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),Lh(t,l)}function Rv(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,u=t.memoizedState;u!==null&&(l=u.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(e),Lh(t,l)}function Av(t,e){return Ni(t,e)}var Ju=null,Na=null,Ts=!1,Fu=!1,Os=!1,Cl=0;function we(t){t!==Na&&t.next===null&&(Na===null?Ju=Na=t:Na=Na.next=t),Fu=!0,Ts||(Ts=!0,zv())}function zn(t,e){if(!Os&&Fu){Os=!0;do for(var l=!1,a=Ju;a!==null;){if(t!==0){var u=a.pendingLanes;if(u===0)var i=0;else{var f=a.suspendedLanes,h=a.pingedLanes;i=(1<<31-oe(42|t)+1)-1,i&=u&~(f&~h),i=i&201326741?i&201326741|1:i?i|2:0}i!==0&&(l=!0,Xh(a,i))}else i=ut,i=In(a,a===Ot?i:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(i&3)===0||Va(a,i)||(l=!0,Xh(a,i));a=a.next}while(l);Os=!1}}function Cv(){Yh()}function Yh(){Fu=Ts=!1;var t=0;Cl!==0&&Bv()&&(t=Cl);for(var e=fe(),l=null,a=Ju;a!==null;){var u=a.next,i=wh(a,e);i===0?(a.next=null,l===null?Ju=u:l.next=u,u===null&&(Na=l)):(l=a,(t!==0||(i&3)!==0)&&(Fu=!0)),a=u}Yt!==0&&Yt!==5||zn(t),Cl!==0&&(Cl=0)}function wh(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,u=t.expirationTimes,i=t.pendingLanes&-62914561;0h)break;var D=v.transferSize,j=v.initiatorType;D&&Wh(j)&&(v=v.responseEnd,f+=D*(v"u"?null:document;function fd(t,e,l){var a=Ha;if(a&&typeof e=="string"&&e){var u=Oe(e);u='link[rel="'+t+'"][href="'+u+'"]',typeof l=="string"&&(u+='[crossorigin="'+l+'"]'),sd.has(u)||(sd.add(u),t={rel:t,crossOrigin:l,href:e},a.querySelector(u)===null&&(e=a.createElement("link"),Jt(e,"link",t),wt(e),a.head.appendChild(e)))}}function Kv(t){il.D(t),fd("dns-prefetch",t,null)}function Jv(t,e){il.C(t,e),fd("preconnect",t,e)}function Fv(t,e,l){il.L(t,e,l);var a=Ha;if(a&&t&&e){var u='link[rel="preload"][as="'+Oe(e)+'"]';e==="image"&&l&&l.imageSrcSet?(u+='[imagesrcset="'+Oe(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(u+='[imagesizes="'+Oe(l.imageSizes)+'"]')):u+='[href="'+Oe(t)+'"]';var i=u;switch(e){case"style":i=qa(t);break;case"script":i=Ba(t)}xe.has(i)||(t=O({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),xe.set(i,t),a.querySelector(u)!==null||e==="style"&&a.querySelector(xn(i))||e==="script"&&a.querySelector(Un(i))||(e=a.createElement("link"),Jt(e,"link",t),wt(e),a.head.appendChild(e)))}}function kv(t,e){il.m(t,e);var l=Ha;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",u='link[rel="modulepreload"][as="'+Oe(a)+'"][href="'+Oe(t)+'"]',i=u;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":i=Ba(t)}if(!xe.has(i)&&(t=O({rel:"modulepreload",href:t},e),xe.set(i,t),l.querySelector(u)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Un(i)))return}a=l.createElement("link"),Jt(a,"link",t),wt(a),l.head.appendChild(a)}}}function $v(t,e,l){il.S(t,e,l);var a=Ha;if(a&&t){var u=na(a).hoistableStyles,i=qa(t);e=e||"default";var f=u.get(i);if(!f){var h={loading:0,preload:null};if(f=a.querySelector(xn(i)))h.loading=5;else{t=O({rel:"stylesheet",href:t,"data-precedence":e},l),(l=xe.get(i))&&Ls(t,l);var v=f=a.createElement("link");wt(v),Jt(v,"link",t),v._p=new Promise(function(A,D){v.onload=A,v.onerror=D}),v.addEventListener("load",function(){h.loading|=1}),v.addEventListener("error",function(){h.loading|=2}),h.loading|=4,Pu(f,e,a)}f={type:"stylesheet",instance:f,count:1,state:h},u.set(i,f)}}}function Wv(t,e){il.X(t,e);var l=Ha;if(l&&t){var a=na(l).hoistableScripts,u=Ba(t),i=a.get(u);i||(i=l.querySelector(Un(u)),i||(t=O({src:t,async:!0},e),(e=xe.get(u))&&Ys(t,e),i=l.createElement("script"),wt(i),Jt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function Iv(t,e){il.M(t,e);var l=Ha;if(l&&t){var a=na(l).hoistableScripts,u=Ba(t),i=a.get(u);i||(i=l.querySelector(Un(u)),i||(t=O({src:t,async:!0,type:"module"},e),(e=xe.get(u))&&Ys(t,e),i=l.createElement("script"),wt(i),Jt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function rd(t,e,l,a){var u=(u=lt.current)?Iu(u):null;if(!u)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=qa(l.href),l=na(u).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=qa(l.href);var i=na(u).hoistableStyles,f=i.get(t);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(t,f),(i=u.querySelector(xn(t)))&&!i._p&&(f.instance=i,f.state.loading=5),xe.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},xe.set(t,l),i||Pv(u,t,l,f.state))),e&&a===null)throw Error(r(528,""));return f}if(e&&a!==null)throw Error(r(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=Ba(l),l=na(u).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function qa(t){return'href="'+Oe(t)+'"'}function xn(t){return'link[rel="stylesheet"]['+t+"]"}function od(t){return O({},t,{"data-precedence":t.precedence,precedence:null})}function Pv(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),Jt(e,"link",l),wt(e),t.head.appendChild(e))}function Ba(t){return'[src="'+Oe(t)+'"]'}function Un(t){return"script[async]"+t}function hd(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+Oe(l.href)+'"]');if(a)return e.instance=a,wt(a),a;var u=O({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),wt(a),Jt(a,"style",u),Pu(a,l.precedence,t),e.instance=a;case"stylesheet":u=qa(l.href);var i=t.querySelector(xn(u));if(i)return e.state.loading|=4,e.instance=i,wt(i),i;a=od(l),(u=xe.get(u))&&Ls(a,u),i=(t.ownerDocument||t).createElement("link"),wt(i);var f=i;return f._p=new Promise(function(h,v){f.onload=h,f.onerror=v}),Jt(i,"link",a),e.state.loading|=4,Pu(i,l.precedence,t),e.instance=i;case"script":return i=Ba(l.src),(u=t.querySelector(Un(i)))?(e.instance=u,wt(u),u):(a=l,(u=xe.get(i))&&(a=O({},l),Ys(a,u)),t=t.ownerDocument||t,u=t.createElement("script"),wt(u),Jt(u,"link",a),t.head.appendChild(u),e.instance=u);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,Pu(a,l.precedence,t));return e.instance}function Pu(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=a.length?a[a.length-1]:null,i=u,f=0;f title"):null)}function t0(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;return e.rel==="stylesheet"?(t=e.disabled,typeof e.precedence=="string"&&t==null):!0;case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function yd(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function e0(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var u=qa(a.href),i=e.querySelector(xn(u));if(i){e=i._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=ei.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=i,wt(i);return}i=e.ownerDocument||e,a=od(a),(u=xe.get(u))&&Ls(a,u),i=i.createElement("link"),wt(i);var f=i;f._p=new Promise(function(h,v){f.onload=h,f.onerror=v}),Jt(i,"link",a),l.instance=i}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=ei.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var ws=0;function l0(t,e){return t.stylesheets&&t.count===0&&ai(t,t.stylesheets),0ws?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(u)}}:null}function ei(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)ai(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var li=null;function ai(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,li=new Map,e.forEach(a0,t),li=null,ei.call(t))}function a0(t,e){if(!(e.state.loading&4)){var l=li.get(t);if(l)var a=l.get(null);else{l=new Map,li.set(t,l);for(var u=t.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(c){console.error(c)}}return n(),$s.exports=S0(),$s.exports}var E0=b0();var df=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,im=/^[\\/]{2}/;function T0(n,c){return c+n.replace(/\\/g,"/")}var Ld="popstate";function Yd(n){return typeof n=="object"&&n!=null&&"pathname"in n&&"search"in n&&"hash"in n&&"state"in n&&"key"in n}function O0(n={}){function c(r,o){let d=o.state?.masked,{pathname:m,search:b,hash:y}=d||r.location;return af("",{pathname:m,search:b,hash:y},o.state&&o.state.usr||null,o.state&&o.state.key||"default",d?{pathname:r.location.pathname,search:r.location.search,hash:r.location.hash}:void 0)}function s(r,o){return typeof o=="string"?o:Yn(o)}return A0(c,s,null,n)}function Lt(n,c){if(n===!1||n===null||typeof n>"u")throw new Error(c)}function qe(n,c){if(!n){typeof console<"u"&&console.warn(c);try{throw new Error(c)}catch{}}}function R0(){return Math.random().toString(36).substring(2,10)}function wd(n,c){return{usr:n.state,key:n.key,idx:c,masked:n.mask?{pathname:n.pathname,search:n.search,hash:n.hash}:void 0}}function af(n,c,s=null,r,o){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof c=="string"?Zn(c):c,state:s,key:c&&c.key||r||R0(),mask:o}}function Yn({pathname:n="/",search:c="",hash:s=""}){return c&&c!=="?"&&(n+=c.charAt(0)==="?"?c:"?"+c),s&&s!=="#"&&(n+=s.charAt(0)==="#"?s:"#"+s),n}function Zn(n){let c={};if(n){let s=n.indexOf("#");s>=0&&(c.hash=n.substring(s),n=n.substring(0,s));let r=n.indexOf("?");r>=0&&(c.search=n.substring(r),n=n.substring(0,r)),n&&(c.pathname=n)}return c}function A0(n,c,s,r={}){let{window:o=document.defaultView,v5Compat:d=!1}=r,m=o.history,b="POP",y=null,p=T();p==null&&(p=0,m.replaceState({...m.state,idx:p},""));function T(){return(m.state||{idx:null}).idx}function O(){b="POP";let q=T(),X=q==null?null:q-p;p=q,y&&y({action:b,location:B.location,delta:X})}function _(q,X){b="PUSH";let G=Yd(q)?q:af(B.location,q,X);p=T()+1;let V=wd(G,p),ct=B.createHref(G.mask||G);try{m.pushState(V,"",ct)}catch(st){if(st instanceof DOMException&&st.name==="DataCloneError")throw st;o.location.assign(ct)}d&&y&&y({action:b,location:B.location,delta:1})}function L(q,X){b="REPLACE";let G=Yd(q)?q:af(B.location,q,X);p=T();let V=wd(G,p),ct=B.createHref(G.mask||G);m.replaceState(V,"",ct),d&&y&&y({action:b,location:B.location,delta:0})}function Q(q){return C0(o,q)}let B={get action(){return b},get location(){return n(o,m)},listen(q){if(y)throw new Error("A history only accepts one active listener");return o.addEventListener(Ld,O),y=q,()=>{o.removeEventListener(Ld,O),y=null}},createHref(q){return c(o,q)},createURL:Q,encodeLocation(q){let X=Q(q);return{pathname:X.pathname,search:X.search,hash:X.hash}},push:_,replace:L,go(q){return m.go(q)}};return B}function C0(n,c,s=!1){let r="http://localhost";n&&(r=n.location.origin!=="null"?n.location.origin:n.location.href),Lt(r,"No window.location.(origin|href) available to create URL");let o=typeof c=="string"?c:Yn(c);return o=o.replace(/ $/,"%20"),!s&&im.test(o)&&(o=r+o),new URL(o,r)}function cm(n,c,s="/"){return z0(n,c,s,!1)}function z0(n,c,s,r,o){let d=typeof c=="string"?Zn(c):c,m=cl(d.pathname||"/",s);if(m==null)return null;let b=M0(n),y=null,p=L0(m);for(let T=0;y==null&&T{let T={relativePath:p===void 0?m.path||"":p,caseSensitive:m.caseSensitive===!0,childrenIndex:b,route:m};if(T.relativePath.startsWith("/")){if(!T.relativePath.startsWith(r)&&y)return;Lt(T.relativePath.startsWith(r),`Absolute route path "${T.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),T.relativePath=T.relativePath.slice(r.length)}let O=He([r,T.relativePath]),_=s.concat(T);m.children&&m.children.length>0&&(Lt(m.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${O}".`),sm(m.children,c,_,O,y)),!(m.path==null&&!m.index)&&c.push({path:O,score:q0(O,m.index),routesMeta:_.map((L,Q)=>{let[B,q]=om(L.relativePath,L.caseSensitive,Q===_.length-1);return{...L,matcher:B,compiledParams:q}})})};return n.forEach((m,b)=>{if(m.path===""||!m.path?.includes("?"))d(m,b);else for(let y of fm(m.path))d(m,b,!0,y)}),c}function fm(n){let c=n.split("/");if(c.length===0)return[];let[s,...r]=c,o=s.endsWith("?"),d=s.replace(/\?$/,"");if(r.length===0)return o?[d,""]:[d];let m=fm(r.join("/")),b=[];return b.push(...m.map(y=>y===""?d:[d,y].join("/"))),o&&b.push(...m),b.map(y=>n.startsWith("/")&&y===""?"/":y)}function _0(n){n.sort((c,s)=>c.score!==s.score?s.score-c.score:B0(c.routesMeta.map(r=>r.childrenIndex),s.routesMeta.map(r=>r.childrenIndex)))}var D0=/^:[\w-]+$/,x0=3,U0=2,j0=1,N0=10,H0=-2,Gd=n=>n==="*";function q0(n,c){let s=n.split("/"),r=s.length;return s.some(Gd)&&(r+=H0),c&&(r+=U0),s.filter(o=>!Gd(o)).reduce((o,d)=>o+(D0.test(d)?x0:d===""?j0:N0),r)}function B0(n,c){return n.length===c.length&&n.slice(0,-1).every((r,o)=>r===c[o])?n[n.length-1]-c[c.length-1]:0}function Q0(n,c,s=!1){let{routesMeta:r}=n,o={},d="/",m=[];for(let b=0;b{if(T==="*"){let Q=b[_]||"";m=d.slice(0,d.length-Q.length).replace(/(.)\/+$/,"$1")}const L=b[_];return O&&!L?p[T]=void 0:p[T]=(L||"").replace(/%2F/g,"/"),p},{}),pathname:d,pathnameBase:m,pattern:n}}function om(n,c=!1,s=!0){qe(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let r=[],o="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(m,b,y,p,T)=>{if(r.push({paramName:b,isOptional:y!=null}),y){let O=T.charAt(p+m.length);return O&&O!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(r.push({paramName:"*"}),o+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):s?o+="\\/*$":n!==""&&n!=="/"&&(o+="(?:(?=\\/|$))"),[new RegExp(o,c?void 0:"i"),r]}function L0(n){try{return n.split("/").map(c=>decodeURIComponent(c).replace(/\//g,"%2F")).join("/")}catch(c){return qe(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${c}).`),n}}function cl(n,c){if(c==="/")return n;if(!n.toLowerCase().startsWith(c.toLowerCase()))return null;let s=c.endsWith("/")?c.length-1:c.length,r=n.charAt(s);return r&&r!=="/"?null:n.slice(s)||"/"}function Y0(n,c="/"){let{pathname:s,search:r="",hash:o=""}=typeof n=="string"?Zn(n):n,d;return s?(s=hm(s),s.startsWith("/")?d=Xd(s.substring(1),"/"):d=Xd(s,c)):d=c,{pathname:d,search:X0(r),hash:Z0(o)}}function Xd(n,c){let s=Si(c).split("/");return n.split("/").forEach(o=>{o===".."?s.length>1&&s.pop():o!=="."&&s.push(o)}),s.length>1?s.join("/"):"/"}function tf(n,c,s,r){return`Cannot include a '${n}' character in a manually specified \`to.${c}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${s}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function w0(n){return n.filter((c,s)=>s===0||c.route.path&&c.route.path.length>0)}function mf(n){let c=w0(n);return c.map((s,r)=>r===c.length-1?s.pathname:s.pathnameBase)}function Ei(n,c,s,r=!1){let o;typeof n=="string"?o=Zn(n):(o={...n},Lt(!o.pathname||!o.pathname.includes("?"),tf("?","pathname","search",o)),Lt(!o.pathname||!o.pathname.includes("#"),tf("#","pathname","hash",o)),Lt(!o.search||!o.search.includes("#"),tf("#","search","hash",o)));let d=n===""||o.pathname==="",m=d?"/":o.pathname,b;if(m==null)b=s;else{let O=c.length-1;if(!r&&m.startsWith("..")){let _=m.split("/");for(;_[0]==="..";)_.shift(),O-=1;o.pathname=_.join("/")}b=O>=0?c[O]:"/"}let y=Y0(o,b),p=m&&m!=="/"&&m.endsWith("/"),T=(d||m===".")&&s.endsWith("/");return!y.pathname.endsWith("/")&&(p||T)&&(y.pathname+="/"),y}var hm=n=>n.replace(/[\\/]{2,}/g,"/"),He=n=>hm(n.join("/")),Si=n=>n.replace(/\/+$/,""),G0=n=>Si(n).replace(/^\/*/,"/"),X0=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,Z0=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,V0=class{constructor(n,c,s,r=!1){this.status=n,this.statusText=c||"",this.internal=r,s instanceof Error?(this.data=s.toString(),this.error=s):this.data=s}};function K0(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function J0(n){let c=n.map(s=>s.route.path).filter(Boolean);return He(c)||"/"}var dm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function mm(n,c){let s=n;if(typeof s!="string"||!df.test(s))return{absoluteURL:void 0,isExternal:!1,to:s};let r=s,o=!1;if(dm)try{let d=new URL(window.location.href),m=im.test(s)?new URL(T0(s,d.protocol)):new URL(s),b=cl(m.pathname,c);m.origin===d.origin&&b!=null?s=b+m.search+m.hash:o=!0}catch{qe(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:o,to:s}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var ym=["POST","PUT","PATCH","DELETE"];new Set(ym);var F0=["GET",...ym];new Set(F0);var k0=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function $0(n){try{return k0.includes(new URL(n).protocol)}catch{return!1}}var wa=z.createContext(null);wa.displayName="DataRouter";var Ti=z.createContext(null);Ti.displayName="DataRouterState";var vm=z.createContext(!1);function W0(){return z.useContext(vm)}var pm=z.createContext({isTransitioning:!1});pm.displayName="ViewTransition";var I0=z.createContext(new Map);I0.displayName="Fetchers";var P0=z.createContext(null);P0.displayName="Await";var be=z.createContext(null);be.displayName="Navigation";var Oi=z.createContext(null);Oi.displayName="Location";var Ge=z.createContext({outlet:null,matches:[],isDataRoute:!1});Ge.displayName="Route";var yf=z.createContext(null);yf.displayName="RouteError";var gm="REACT_ROUTER_ERROR",tp="REDIRECT",ep="ROUTE_ERROR_RESPONSE";function lp(n){if(n.startsWith(`${gm}:${tp}:{`))try{let c=JSON.parse(n.slice(28));if(typeof c=="object"&&c&&typeof c.status=="number"&&typeof c.statusText=="string"&&typeof c.location=="string"&&typeof c.reloadDocument=="boolean"&&typeof c.replace=="boolean")return c}catch{}}function ap(n){if(n.startsWith(`${gm}:${ep}:{`))try{let c=JSON.parse(n.slice(40));if(typeof c=="object"&&c&&typeof c.status=="number"&&typeof c.statusText=="string")return new V0(c.status,c.statusText,c.data)}catch{}}function np(n,{relative:c}={}){Lt(Ga(),"useHref() may be used only in the context of a component.");let{basename:s,navigator:r}=z.useContext(be),{hash:o,pathname:d,search:m}=Vn(n,{relative:c}),b=d;return s!=="/"&&(b=d==="/"?s:He([s,d])),r.createHref({pathname:b,search:m,hash:o})}function Ga(){return z.useContext(Oi)!=null}function Be(){return Lt(Ga(),"useLocation() may be used only in the context of a component."),z.useContext(Oi).location}var Sm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function bm(n){z.useContext(be).static||z.useLayoutEffect(n)}function Ri(){let{isDataRoute:n}=z.useContext(Ge);return n?pp():up()}function up(){Lt(Ga(),"useNavigate() may be used only in the context of a component.");let n=z.useContext(wa),{basename:c,navigator:s}=z.useContext(be),{matches:r}=z.useContext(Ge),{pathname:o}=Be(),d=JSON.stringify(mf(r)),m=z.useRef(!1);return bm(()=>{m.current=!0}),z.useCallback((y,p={})=>{if(qe(m.current,Sm),!m.current)return;if(typeof y=="number"){s.go(y);return}let T=Ei(y,JSON.parse(d),o,p.relative==="path");n==null&&c!=="/"&&(T.pathname=T.pathname==="/"?c:He([c,T.pathname])),(p.replace?s.replace:s.push)(T,p.state,p)},[c,s,d,o,n])}z.createContext(null);function Vn(n,{relative:c}={}){let{matches:s}=z.useContext(Ge),{pathname:r}=Be(),o=JSON.stringify(mf(s));return z.useMemo(()=>Ei(n,JSON.parse(o),r,c==="path"),[n,o,r,c])}function ip(n,c,s){Lt(Ga(),"useRoutes() may be used only in the context of a component.");let{navigator:r}=z.useContext(be),{matches:o}=z.useContext(Ge),d=o[o.length-1],m=d?d.params:{},b=d?d.pathname:"/",y=d?d.pathnameBase:"/",p=d&&d.route;{let q=p&&p.path||"";Tm(b,!p||q.endsWith("*")||q.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${b}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let T=Be(),O;O=T;let _=O.pathname||"/",L=_;if(y!=="/"){let q=y.replace(/^\//,"").split("/");L="/"+_.replace(/^\//,"").split("/").slice(q.length).join("/")}let Q=s&&s.state.matches.length?s.state.matches.map(q=>Object.assign(q,{route:s.manifest[q.route.id]||q.route})):cm(n,{pathname:L});return qe(p||Q!=null,`No routes matched location "${O.pathname}${O.search}${O.hash}" `),qe(Q==null||Q[Q.length-1].route.element!==void 0||Q[Q.length-1].route.Component!==void 0||Q[Q.length-1].route.lazy!==void 0,`Matched leaf route at location "${O.pathname}${O.search}${O.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`),op(Q&&Q.map(q=>Object.assign({},q,{params:Object.assign({},m,q.params),pathname:He([y,r.encodeLocation?r.encodeLocation(q.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathname]),pathnameBase:q.pathnameBase==="/"?y:He([y,r.encodeLocation?r.encodeLocation(q.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathnameBase])})),o,s)}function cp(){let n=vp(),c=K0(n)?`${n.status} ${n.statusText}`:n instanceof Error?n.message:JSON.stringify(n),s=n instanceof Error?n.stack:null,r="rgba(200,200,200, 0.5)",o={padding:"0.5rem",backgroundColor:r},d={padding:"2px 4px",backgroundColor:r},m=null;return console.error("Error handled by React Router default ErrorBoundary:",n),m=z.createElement(z.Fragment,null,z.createElement("p",null,"💿 Hey developer 👋"),z.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",z.createElement("code",{style:d},"ErrorBoundary")," or"," ",z.createElement("code",{style:d},"errorElement")," prop on your route.")),z.createElement(z.Fragment,null,z.createElement("h2",null,"Unexpected Application Error!"),z.createElement("h3",{style:{fontStyle:"italic"}},c),s?z.createElement("pre",{style:o},s):null,m)}var sp=z.createElement(cp,null),Em=class extends z.Component{constructor(n){super(n),this.state={location:n.location,revalidation:n.revalidation,error:n.error}}static getDerivedStateFromError(n){return{error:n}}static getDerivedStateFromProps(n,c){return c.location!==n.location||c.revalidation!=="idle"&&n.revalidation==="idle"?{error:n.error,location:n.location,revalidation:n.revalidation}:{error:n.error!==void 0?n.error:c.error,location:c.location,revalidation:n.revalidation||c.revalidation}}componentDidCatch(n,c){this.props.onError?this.props.onError(n,c):console.error("React Router caught the following error during render",n)}render(){let n=this.state.error;if(this.context&&typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){const s=ap(n.digest);s&&(n=s)}let c=n!==void 0?z.createElement(Ge.Provider,{value:this.props.routeContext},z.createElement(yf.Provider,{value:n,children:this.props.component})):this.props.children;return this.context?z.createElement(fp,{error:n},c):c}};Em.contextType=vm;var ef=new WeakMap;function fp({children:n,error:c}){let{basename:s}=z.useContext(be);if(typeof c=="object"&&c&&"digest"in c&&typeof c.digest=="string"){let r=lp(c.digest);if(r){let o=ef.get(c);if(o)throw o;let d=mm(r.location,s),m=d.absoluteURL||d.to;if($0(m))throw new Error("Invalid redirect location");if(dm&&!ef.get(c))if(d.isExternal||r.reloadDocument)window.location.href=m;else{const b=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(d.to,{replace:r.replace}));throw ef.set(c,b),b}return z.createElement("meta",{httpEquiv:"refresh",content:`0;url=${m}`})}}return n}function rp({routeContext:n,match:c,children:s}){let r=z.useContext(wa);return r&&r.static&&r.staticContext&&(c.route.errorElement||c.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=c.route.id),z.createElement(Ge.Provider,{value:n},s)}function op(n,c=[],s){let r=s?.state;if(n==null){if(!r)return null;if(r.errors)n=r.matches;else if(c.length===0&&!r.initialized&&r.matches.length>0)n=r.matches;else return null}let o=n,d=r?.errors;if(d!=null){let T=o.findIndex(O=>O.route.id&&d?.[O.route.id]!==void 0);Lt(T>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(d).join(",")}`),o=o.slice(0,Math.min(o.length,T+1))}let m=!1,b=-1;if(s&&r){m=r.renderFallback;for(let T=0;T=0?o=o.slice(0,b+1):o=[o[0]];break}}}}let y=s?.onError,p=r&&y?(T,O)=>{y(T,{location:r.location,params:r.matches?.[0]?.params??{},pattern:J0(r.matches),errorInfo:O})}:void 0;return o.reduceRight((T,O,_)=>{let L,Q=!1,B=null,q=null;r&&(L=d&&O.route.id?d[O.route.id]:void 0,B=O.route.errorElement||sp,m&&(b<0&&_===0?(Tm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),Q=!0,q=null):b===_&&(Q=!0,q=O.route.hydrateFallbackElement||null)));let X=c.concat(o.slice(0,_+1)),G=()=>{let V;return L?V=B:Q?V=q:O.route.Component?V=z.createElement(O.route.Component,null):O.route.element?V=O.route.element:V=T,z.createElement(rp,{match:O,routeContext:{outlet:T,matches:X,isDataRoute:r!=null},children:V})};return r&&(O.route.ErrorBoundary||O.route.errorElement||_===0)?z.createElement(Em,{location:r.location,revalidation:r.revalidation,component:B,error:L,children:G(),routeContext:{outlet:null,matches:X,isDataRoute:!0},onError:p}):G()},null)}function vf(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function hp(n){let c=z.useContext(wa);return Lt(c,vf(n)),c}function dp(n){let c=z.useContext(Ti);return Lt(c,vf(n)),c}function mp(n){let c=z.useContext(Ge);return Lt(c,vf(n)),c}function pf(n){let c=mp(n),s=c.matches[c.matches.length-1];return Lt(s.route.id,`${n} can only be used on routes that contain a unique "id"`),s.route.id}function yp(){return pf("useRouteId")}function vp(){let n=z.useContext(yf),c=dp("useRouteError"),s=pf("useRouteError");return n!==void 0?n:c.errors?.[s]}function pp(){let{router:n}=hp("useNavigate"),c=pf("useNavigate"),s=z.useRef(!1);return bm(()=>{s.current=!0}),z.useCallback(async(o,d={})=>{qe(s.current,Sm),s.current&&(typeof o=="number"?await n.navigate(o):await n.navigate(o,{fromRouteId:c,...d}))},[n,c])}var Zd={};function Tm(n,c,s){!c&&!Zd[n]&&(Zd[n]=!0,qe(!1,s))}z.memo(gp);function gp({routes:n,manifest:c,future:s,state:r,isStatic:o,onError:d}){return ip(n,void 0,{manifest:c,state:r,isStatic:o,onError:d})}function Sp({to:n,replace:c,state:s,relative:r}){Lt(Ga()," may be used only in the context of a component.");let{static:o}=z.useContext(be);qe(!o," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:d}=z.useContext(Ge),{pathname:m}=Be(),b=Ri(),y=Ei(n,mf(d),m,r==="path"),p=JSON.stringify(y);return z.useEffect(()=>{b(JSON.parse(p),{replace:c,state:s,relative:r})},[b,p,r,c,s]),null}function bp({basename:n="/",children:c=null,location:s,navigationType:r="POP",navigator:o,static:d=!1,useTransitions:m}){Lt(!Ga(),"You cannot render a inside another . You should never have more than one in your app.");let b=n.replace(/^\/*/,"/"),y=z.useMemo(()=>({basename:b,navigator:o,static:d,useTransitions:m,future:{}}),[b,o,d,m]);typeof s=="string"&&(s=Zn(s));let{pathname:p="/",search:T="",hash:O="",state:_=null,key:L="default",mask:Q}=s,B=z.useMemo(()=>{let q=cl(p,b);return q==null?null:{location:{pathname:q,search:T,hash:O,state:_,key:L,mask:Q},navigationType:r}},[b,p,T,O,_,L,r,Q]);return qe(B!=null,` is not able to match the URL "${p}${T}${O}" because it does not start with the basename, so the won't render anything.`),B==null?null:z.createElement(be.Provider,{value:y},z.createElement(Oi.Provider,{children:c,value:B}))}var di="get",mi="application/x-www-form-urlencoded";function Ai(n){return typeof HTMLElement<"u"&&n instanceof HTMLElement}function Ep(n){return Ai(n)&&n.tagName.toLowerCase()==="button"}function Tp(n){return Ai(n)&&n.tagName.toLowerCase()==="form"}function Op(n){return Ai(n)&&n.tagName.toLowerCase()==="input"}function Rp(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function Ap(n,c){return n.button===0&&(!c||c==="_self")&&!Rp(n)}var oi=null;function Cp(){if(oi===null)try{new FormData(document.createElement("form"),0),oi=!1}catch{oi=!0}return oi}var zp=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function lf(n){return n!=null&&!zp.has(n)?(qe(!1,`"${n}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${mi}"`),null):n}function Mp(n,c){let s,r,o,d,m;if(Tp(n)){let b=n.getAttribute("action");r=b?cl(b,c):null,s=n.getAttribute("method")||di,o=lf(n.getAttribute("enctype"))||mi,d=new FormData(n)}else if(Ep(n)||Op(n)&&(n.type==="submit"||n.type==="image")){let b=n.form;if(b==null)throw new Error('Cannot submit a