// app.jsx — API-driven App for the Merger Control Navigator. // Presentational components (StatsStrip, Filters, JurisdictionTable, // RegionalMatrix, PartiesCard, DetailPanel, OverrideModal) are reused from // dashboard.jsx / detail.jsx unchanged. const { useState, useMemo, useEffect, useCallback, useRef } = React; const { needsAttention } = window.MC_HELPERS; const API = window.MC_API; const CLERK = window.MC_CLERK; function Login({ onOk }) { const [pw, setPw] = useState(""); const [err, setErr] = useState(null); const submit = async () => { try { await API.login(pw); onOk(); } catch { setErr("Incorrect password."); } }; return (
MC

Merger Control Navigator

Internal test instance — enter the access password.

setPw(e.target.value)} onKeyDown={(e) => e.key === "Enter" && submit()} /> {err &&
{err}
}
); } // Clerk's mounted insists on routing its "Sign up" footer link to // the hosted Account Portal (Clerk's signUpUrl prop is ignored in this // instance's config). Workaround: hide Clerk's own footer, mount in // "virtual" routing mode (no URL changes), and put our own React-state // switch underneath. The two flows then share the same appearance and never // touch Clerk's hosted pages. const CLERK_APPEARANCE = { variables: { colorPrimary: "#6B176B", borderRadius: "10px", fontFamily: 'Inter, system-ui, -apple-system, "Segoe UI", sans-serif', }, elements: { // Multiple keys for footer/dev-mode badge across Clerk versions — // setting all of them is the safe way to suppress them all. footer: { display: "none" }, footerAction: { display: "none" }, footerActionLink: { display: "none" }, footerActionText: { display: "none" }, developmentBadge: { display: "none" }, }, }; function ClerkGate() { const ref = useRef(null); const [mode, setMode] = useState("signin"); // "signin" | "signup" useEffect(() => { if (!ref.current) return; const node = ref.current; const opts = { appearance: CLERK_APPEARANCE, routing: "virtual" }; if (mode === "signup") CLERK.mountSignUp(node, opts); else CLERK.mountSignIn(node, opts); return () => { const c = CLERK.get(); if (!c) return; if (mode === "signup") c.unmountSignUp(node); else c.unmountSignIn(node); }; }, [mode]); return (
{mode === "signin" ? ( <>Don't have an account?{" "} ) : ( <>Already have an account?{" "} )}
); } function ClerkUserButton() { const ref = useRef(null); useEffect(() => { if (ref.current) CLERK.mountUserButton(ref.current, { afterSignOutUrl: "/" }); }, []); return
; } const ROLE_OPTIONS = [ ["", "Skip this sheet"], ["buyer", "Buyer"], ["target", "Target"], ["seller", "Seller"], ["jv_parent", "JV parent"], ["jv_entity", "JV entity"], ]; const CONFIDENCE_LABEL = { high: "High", medium: "Medium", low: "Low" }; function MappingReviewModal({ proposal, file, onClose, onConfirmed }) { const [mapping, setMapping] = useState(proposal); const [name, setName] = useState(file.name.replace(/\.xlsx$/i, "")); const [busy, setBusy] = useState(false); const [err, setErr] = useState(null); const updateSheet = (idx, patch) => setMapping((m) => ({ ...m, sheets: m.sheets.map((s, i) => i === idx ? { ...s, ...patch } : s), })); const updateRoot = (patch) => setMapping((m) => ({ ...m, ...patch })); const isJv = mapping.transaction_type === "jv_creation"; const partyRows = mapping.sheets.filter((s) => s.role); const skipRows = mapping.sheets.filter((s) => !s.role); const confirm = async () => { if (!name.trim()) { setErr("Matter name is required."); return; } if (!partyRows.length) { setErr("At least one sheet must be assigned a party role."); return; } setBusy(true); setErr(null); try { const matter = await API.createFromMapping({ file, name, mapping }); await onConfirmed(matter); } catch (e) { console.error("createFromMapping failed", e); setErr(e && e.unauthorized ? "Session expired — please sign in again." : (e && e.message) ? e.message : "Request failed"); } finally { setBusy(false); } }; return (
e.stopPropagation()}>

Review mapping

The AI inferred the structure of {file.name}. Adjust anything that looks wrong before screening runs. AI confidence: {CONFIDENCE_LABEL[mapping.confidence] || mapping.confidence}
setName(e.target.value)} placeholder="e.g. Project Botticelli" />
updateRoot({ reporting_year: +e.target.value })} />
{isJv && (
)}