<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Clarity QBO Portal</title>
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.5/babel.min.js"></script>
  <style>
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: 'Inter', 'Segoe UI', sans-serif; background: #F7F9FB; }
    @keyframes spin { to { transform: rotate(360deg); } }
    select, input, button, a { font-family: inherit; }
    /* QBO callback handler — shown briefly when popup redirects back */
    #callback-screen {
      display: none; position: fixed; inset: 0; background: #1B2A3B;
      align-items: center; justify-content: center; flex-direction: column;
      color: white; font-size: 16px; gap: 12px; z-index: 9999;
    }
    #callback-screen.active { display: flex; }
  </style>
</head>
<body>

<!-- OAuth callback handler — this div shows briefly when QBO redirects back -->
<div id="callback-screen">
  <div style="font-size:28px">✅</div>
  <div style="font-weight:700">QuickBooks Connected</div>
  <div style="font-size:13px;opacity:.6">Closing window…</div>
</div>

<div id="root"></div>

<script>
// ── OAuth Callback Detection ──────────────────────────────────────────────────
// If this page is loaded as the OAuth redirect target (i.e. URL contains ?code=)
// show the callback screen briefly — the parent portal window is polling for this.
(function() {
  const params = new URLSearchParams(window.location.search);
  if (params.has('code') || params.has('error')) {
    document.getElementById('callback-screen').classList.add('active');
    document.getElementById('root').style.display = 'none';
    // Parent window is polling popup.location.href — just stay here so it can read the URL
    // It will close this popup automatically once it reads the code
  }
})();
</script>

<script type="text/babel">
const { useState, useCallback, useRef } = React;

// ═══════════════════════════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════════════════════════
const CONFIG = {
  CLIENT_ID:    "ABRF8oL45prebalM8i8Dm3YGW3eOymjP82kVZzhCiH1GNJuv3j",
  PROXY_URL:    "https://clarity-qbo-proxy.clarityaccounting.workers.dev",
  REDIRECT_URI: window.location.origin + "/qbo-callback",
  QBO_BASE:     "https://quickbooks.api.intuit.com",
  SCOPE:        "com.intuit.quickbooks.accounting",
  AUTH_URL:     "https://appcenter.intuit.com/connect/oauth2",
  SENTRY_DSN:   "https://7e1177b2f7614902966bfcca638effaf@o4511612197273600.ingest.us.sentry.io/4511612212740096",
  SUPPORT: {
    name:    "Clarity Business Books & Accounting",
    email:   "info@claritysmallbusinessaccounting.com",
    phone:   "818-306-2201",
    hours:   "Mon–Fri, 9 AM – 5 PM PT",
    website: "https://claritysmallbusinessaccounting.com",
  },
};

// ═══════════════════════════════════════════════════════════════════════════════
// DESIGN TOKENS
// ═══════════════════════════════════════════════════════════════════════════════
const T = {
  navy:"#1B2A3B", navyMid:"#243447", navyDk:"#111D28",
  green:"#2E7D5B", greenLt:"#3DA373", greenPl:"#EAF4EF",
  slate:"#4A5568", mist:"#F7F9FB", border:"#D8E2EC",
  text:"#1B2A3B", sub:"#637082", white:"#FFFFFF",
  error:"#C0392B", errorBg:"#FDECEA",
  warn:"#E67E22", warnBg:"#FEF3E2",
  gold:"#B7950B", goldBg:"#FEFCE8",
};

// ═══════════════════════════════════════════════════════════════════════════════
// SENTRY
// ═══════════════════════════════════════════════════════════════════════════════
const Sentry = (() => {
  function parseDsn(dsn) {
    try {
      const u = new URL(dsn);
      return `https://${u.hostname}/api/${u.pathname.replace("/","")}/envelope/?sentry_key=${u.username}&sentry_version=7`;
    } catch { return null; }
  }
  function send(level, message, extras = {}) {
    const endpoint = parseDsn(CONFIG.SENTRY_DSN);
    if (!endpoint) { console[level==="error"?"error":"log"]("[Clarity QBO]", message, extras); return; }
    const eid = crypto.randomUUID().replace(/-/g,"");
    const ts  = new Date().toISOString();
    const envelope = [
      JSON.stringify({ event_id: eid, sent_at: ts }),
      JSON.stringify({ type: "event" }),
      JSON.stringify({ event_id:eid, timestamp:ts, level, message, tags:{app:"clarity-qbo-portal"}, extra:extras, request:{url:window.location.href} }),
    ].join("\n");
    fetch(endpoint, { method:"POST", body:envelope }).catch(()=>{});
  }
  return { error:(m,e)=>send("error",m,e), warning:(m,e)=>send("warning",m,e), info:(m,e)=>send("info",m,e) };
})();

// ═══════════════════════════════════════════════════════════════════════════════
// OAUTH
// ═══════════════════════════════════════════════════════════════════════════════
const _csrfNonces = new Map();

function buildAuthUrl(clientId) {
  const nonce = crypto.randomUUID();
  const state = `${clientId}.${nonce}`;
  _csrfNonces.set(nonce, clientId);
  const params = new URLSearchParams({
    client_id: CONFIG.CLIENT_ID,
    redirect_uri: CONFIG.REDIRECT_URI,
    response_type: "code",
    scope: CONFIG.SCOPE,
    state,
  });
  return `${CONFIG.AUTH_URL}?${params}`;
}

function openOAuthPopup(clientId) {
  return new Promise((resolve, reject) => {
    const url   = buildAuthUrl(clientId);
    const popup = window.open(url, "qbo_oauth", "width=620,height=720,left=200,top=100");
    if (!popup) {
      Sentry.error("OAuth popup blocked", { clientId });
      reject(new Error("Popup blocked — allow popups for localhost:3000 in Chrome settings."));
      return;
    }
    const timer = setInterval(() => {
      try {
        if (popup.closed) { clearInterval(timer); reject(new Error("Auth window closed before completing.")); return; }
        const loc = popup.location.href;
        if (loc.includes(window.location.origin) || loc.includes("localhost:3000")) {
          clearInterval(timer);
          popup.close();
          const u     = new URL(loc);
          const code  = u.searchParams.get("code");
          const realm = u.searchParams.get("realmId");
          const state = u.searchParams.get("state");
          const err   = u.searchParams.get("error");
          if (err)  { Sentry.error("OAuth error", { err, clientId }); reject(new Error(err)); return; }
          if (!code){ Sentry.error("No auth code", { clientId }); reject(new Error("No auth code returned.")); return; }
          const [, returnedNonce] = (state||"").split(".");
          if (!returnedNonce || !_csrfNonces.has(returnedNonce)) {
            Sentry.error("CSRF validation failed", { state, clientId });
            reject(new Error("Security error: invalid state parameter. Please try again.")); return;
          }
          _csrfNonces.delete(returnedNonce);
          Sentry.info("OAuth success", { clientId, hasRealm: !!realm });
          resolve({ code, realmId: realm, clientId });
        }
      } catch (_) { /* cross-origin — keep polling */ }
    }, 300);
    setTimeout(() => {
      clearInterval(timer);
      if (!popup.closed) popup.close();
      Sentry.error("OAuth timed out", { clientId });
      reject(new Error("Auth timed out."));
    }, 300_000);
  });
}

async function exchangeCode(code) {
  const res  = await fetch(`${CONFIG.PROXY_URL}/token`, {
    method: "POST", headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ code, redirect_uri: CONFIG.REDIRECT_URI }),
  });
  const data = await res.json();
  if (!res.ok) {
    const msg = data.error || "Token exchange failed";
    Sentry.error("Token exchange failed", { status: res.status, error: msg });
    throw new Error(msg);
  }
  Sentry.info("Token exchange succeeded");
  return data;
}

async function refreshAccessToken(token) {
  const res  = await fetch(`${CONFIG.PROXY_URL}/refresh`, {
    method: "POST", headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token: token }),
  });
  const data = await res.json();
  if (!res.ok) {
    Sentry.error("Token refresh failed", { status: res.status });
    throw new Error(data.error || "Token refresh failed");
  }
  Sentry.info("Token refresh succeeded");
  return data;
}

// ═══════════════════════════════════════════════════════════════════════════════
// QBO API
// ═══════════════════════════════════════════════════════════════════════════════
async function qboReport(accessToken, realmId, reportName, params = {}) {
  // Route through Worker proxy to avoid CORS restrictions on direct QBO API calls
  const res = await fetch(`${CONFIG.PROXY_URL}/report`, {
    method:  "POST",
    headers: { "Content-Type": "application/json" },
    body:    JSON.stringify({ access_token: accessToken, realm_id: realmId, report_name: reportName, params }),
  });
  const data = await res.json();
  const tid  = data._intuit_tid || null;
  if (tid) console.log(`[QBO] intuit_tid: ${tid} — ${reportName}`);
  if (res.status === 401 || data?.Fault?.Error?.[0]?.code === "3200") {
    Sentry.error("QBO 401 — token expired", { reportName, intuit_tid: tid });
    throw Object.assign(new Error("TOKEN_EXPIRED"), { intuit_tid: tid });
  }
  if (!res.ok) {
    const msg = data?.Fault?.Error?.[0]?.Message || `QBO error ${res.status}`;
    Sentry.error("QBO API error", { reportName, status: res.status, message: msg, intuit_tid: tid });
    throw Object.assign(new Error(`${msg}${tid ? ` (intuit_tid: ${tid})` : ""}`), { intuit_tid: tid });
  }
  Sentry.info("QBO report fetched", { reportName, intuit_tid: tid });
  return data;
}

// ═══════════════════════════════════════════════════════════════════════════════
// REPORT DEFINITIONS
// ═══════════════════════════════════════════════════════════════════════════════
const REPORTS = [
  { value:"ProfitAndLoss",   label:"Profit & Loss",            params:(f,t)=>({start_date:f,end_date:t}) },
  { value:"BalanceSheet",    label:"Balance Sheet",             params:(_,t)=>({end_date:t}) },
  { value:"AgedReceivables", label:"A/R Aging Summary",         params:()=>({}) },
  { value:"AgedPayables",    label:"A/P Aging Summary",         params:()=>({}) },
  { value:"CashFlow",        label:"Statement of Cash Flows",   params:(f,t)=>({start_date:f,end_date:t}) },
  { value:"GeneralLedger",   label:"General Ledger",            params:(f,t)=>({start_date:f,end_date:t}) },
  { value:"TransactionList", label:"Transaction List",          params:(f,t)=>({start_date:f,end_date:t}) },
];

// ═══════════════════════════════════════════════════════════════════════════════
// UI COMPONENTS
// ═══════════════════════════════════════════════════════════════════════════════
function Badge({ status }) {
  const map = {
    connected:    { bg:T.greenPl, text:T.green, dot:T.green, label:"Connected" },
    pending:      { bg:T.warnBg,  text:T.warn,  dot:T.warn,  label:"Connecting…" },
    disconnected: { bg:T.errorBg, text:T.error, dot:T.error, label:"Not Connected" },
    expired:      { bg:T.goldBg,  text:T.gold,  dot:T.gold,  label:"Token Expired" },
  };
  const s = map[status] || map.disconnected;
  return (
    <span style={{display:"inline-flex",alignItems:"center",gap:5,background:s.bg,color:s.text,borderRadius:20,padding:"2px 10px",fontSize:12,fontWeight:600}}>
      <span style={{width:6,height:6,borderRadius:"50%",background:s.dot,display:"inline-block"}}/>
      {s.label}
    </span>
  );
}

function Btn({ onClick, variant="primary", children, small, disabled, fullWidth }) {
  const vs = {
    primary:   {background:T.green,   color:T.white, border:"none"},
    secondary: {background:T.mist,    color:T.navy,  border:`1px solid ${T.border}`},
    danger:    {background:T.errorBg, color:T.error, border:"1px solid #f5c6c2"},
  };
  return (
    <button onClick={disabled?undefined:onClick} style={{
      ...vs[variant]||vs.primary, borderRadius:7, cursor:disabled?"not-allowed":"pointer",
      fontWeight:600, fontSize:small?12:14, padding:small?"5px 12px":"9px 18px",
      opacity:disabled?.5:1, width:fullWidth?"100%":undefined,
      display:"inline-flex", alignItems:"center", justifyContent:"center", gap:6,
    }}>{children}</button>
  );
}

function Card({ children, style }) {
  return <div style={{background:T.white,border:`1px solid ${T.border}`,borderRadius:12,padding:20,...style}}>{children}</div>;
}

function Spinner() {
  return <span style={{display:"inline-block",width:14,height:14,border:"2px solid rgba(255,255,255,.3)",borderTop:"2px solid white",borderRadius:"50%",animation:"spin .7s linear infinite"}}/>;
}

function ReportTable({ report }) {
  if (!report?.Rows?.Row?.length) return <div style={{padding:"24px 0",textAlign:"center",color:T.sub,fontSize:14}}>No data returned for this period.</div>;

  const renderRow = (row, depth=0) => {
    if (!row) return null;
    const pad = 16 + depth * 16;
    if (row.type === "Section") return (
      <div key={Math.random()}>
        {row.Header?.ColData && (
          <div style={{display:"flex",justifyContent:"space-between",padding:`9px 16px 9px ${pad}px`,background:depth===0?T.navyMid:"#2D4A63",color:T.white,fontSize:12,fontWeight:700,textTransform:"uppercase",letterSpacing:".7px"}}>
            <span>{row.Header.ColData[0]?.value}</span>
            {row.Header.ColData[1]?.value && <span>{row.Header.ColData[1].value}</span>}
          </div>
        )}
        {row.Rows?.Row?.map((r,i) => <div key={i}>{renderRow(r, depth+1)}</div>)}
        {row.Summary?.ColData && (
          <div style={{display:"flex",justifyContent:"space-between",padding:`9px 16px 9px ${pad}px`,background:depth===0?T.navy:"#1E3850",color:T.white,fontWeight:700,fontSize:13,borderTop:"1px solid rgba(255,255,255,.1)"}}>
            <span>{row.Summary.ColData[0]?.value}</span>
            <span>{row.Summary.ColData[1]?.value||"—"}</span>
          </div>
        )}
      </div>
    );
    if (row.type === "Data") {
      const cols = row.ColData||[];
      return (
        <div style={{display:"flex",justifyContent:"space-between",padding:`8px 16px 8px ${pad}px`,borderBottom:`1px solid ${T.border}`,fontSize:13}}>
          <span style={{color:T.text}}>{cols[0]?.value||"—"}</span>
          <span style={{color:T.navy,fontWeight:600}}>{cols[1]?.value||cols[cols.length-1]?.value||"—"}</span>
        </div>
      );
    }
    return null;
  };
  return (
    <div style={{background:T.mist,border:`1px solid ${T.border}`,borderRadius:10,overflow:"hidden",marginTop:16}}>
      {report.Rows.Row.map((r,i) => <div key={i}>{renderRow(r)}</div>)}
    </div>
  );
}

function SupportPanel({ onClose }) {
  const s = CONFIG.SUPPORT;
  const Row = ({ icon, label, value, href, bg }) => (
    <a href={href} target="_blank" rel="noreferrer" style={{display:"flex",alignItems:"center",gap:14,padding:"13px 16px",background:bg||T.mist,borderRadius:9,textDecoration:"none",border:`1px solid ${T.border}`,marginBottom:10}}>
      <div style={{width:36,height:36,borderRadius:8,background:T.navy,display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0,fontSize:18}}>{icon}</div>
      <div>
        <div style={{fontSize:11,fontWeight:700,color:T.sub,textTransform:"uppercase",letterSpacing:".5px",marginBottom:2}}>{label}</div>
        <div style={{fontSize:14,fontWeight:600,color:T.navy}}>{value}</div>
      </div>
    </a>
  );
  return (
    <>
      <div onClick={onClose} style={{position:"fixed",inset:0,background:"rgba(17,29,40,.55)",zIndex:200,backdropFilter:"blur(2px)"}}/>
      <div style={{position:"fixed",top:"50%",left:"50%",transform:"translate(-50%,-50%)",zIndex:201,width:420,maxWidth:"calc(100vw - 32px)"}}>
        <Card style={{padding:0,overflow:"hidden",boxShadow:"0 12px 40px rgba(0,0,0,.22)"}}>
          <div style={{background:T.navy,padding:"18px 22px",display:"flex",justifyContent:"space-between",alignItems:"center"}}>
            <div>
              <div style={{color:T.white,fontWeight:700,fontSize:15}}>Support</div>
              <div style={{color:"#5B7A94",fontSize:12,marginTop:2}}>{s.name}</div>
            </div>
            <button onClick={onClose} style={{background:"none",border:"none",color:"#5B7A94",fontSize:20,cursor:"pointer"}}>✕</button>
          </div>
          <div style={{padding:"22px 22px 24px"}}>
            <div style={{fontSize:13,color:T.sub,marginBottom:16,lineHeight:1.7}}>Having trouble? Reach out and we'll help you get sorted.</div>
            <Row icon="✉️" label="Email Support" value={s.email} href={`mailto:${s.email}?subject=Clarity QBO Portal Support`} bg={T.greenPl}/>
            <Row icon="📞" label="Phone" value={`${s.phone} · ${s.hours}`} href={`tel:${s.phone.replace(/[^0-9]/g,"")}`}/>
            <Row icon="🌐" label="Website" value={s.website.replace("https://", "")} href={s.website}/>
            <div style={{marginTop:14,padding:"10px 14px",background:"#F0F4F8",borderRadius:8,fontSize:12,color:T.sub,lineHeight:1.6}}>
              <strong style={{color:T.slate}}>Built on QuickBooks API.</strong> Not affiliated with Intuit. For QBO product support visit <a href="https://quickbooks.intuit.com/learn-support/" target="_blank" rel="noreferrer" style={{color:T.green}}>quickbooks.intuit.com/learn-support</a>.
            </div>
          </div>
        </Card>
      </div>
    </>
  );
}

// ═══════════════════════════════════════════════════════════════════════════════
// MAIN APP
// ═══════════════════════════════════════════════════════════════════════════════
function App() {
  const [clients, setClients]       = useState([]);
  const [activeId, setActiveId]     = useState(null);
  const [view, setView]             = useState("dashboard");
  const [newName, setNewName]       = useState("");
  const [newRealm, setNewRealm]     = useState("");
  const [report, setReport]         = useState(null);
  const [reportType, setReportType] = useState("ProfitAndLoss");
  const [dateFrom, setDateFrom]     = useState("2026-01-01");
  const [dateTo, setDateTo]         = useState(new Date().toISOString().slice(0,10));
  const [loading, setLoading]       = useState(false);
  const [error, setError]           = useState("");
  const [showSupport, setShowSupport] = useState(false);
  const refreshTimers = useRef({});

  const activeClient = clients.find(c => c.id === activeId);

  const scheduleRefresh = useCallback((clientId, refreshToken, expiresIn) => {
    if (refreshTimers.current[clientId]) clearTimeout(refreshTimers.current[clientId]);
    const delay = Math.max((expiresIn - 300) * 1000, 10_000);
    refreshTimers.current[clientId] = setTimeout(async () => {
      try {
        const tokens = await refreshAccessToken(refreshToken);
        setClients(prev => prev.map(c => c.id===clientId ? {
          ...c, accessToken:tokens.access_token, refreshToken:tokens.refresh_token||c.refreshToken,
          tokenExpiry:Date.now()+tokens.expires_in*1000, status:"connected",
        } : c));
        scheduleRefresh(clientId, tokens.refresh_token||refreshToken, tokens.expires_in);
      } catch { setClients(prev => prev.map(c => c.id===clientId ? {...c,status:"expired"} : c)); }
    }, delay);
  }, []);

  const connectClient = useCallback(async (clientId) => {
    setError("");
    setClients(prev => prev.map(c => c.id===clientId ? {...c,status:"pending"} : c));
    try {
      const { code, realmId } = await openOAuthPopup(clientId);
      const tokens = await exchangeCode(code);
      setClients(prev => prev.map(c => c.id===clientId ? {
        ...c, realmId:realmId||c.realmId, accessToken:tokens.access_token,
        refreshToken:tokens.refresh_token, tokenExpiry:Date.now()+tokens.expires_in*1000, status:"connected",
      } : c));
      scheduleRefresh(clientId, tokens.refresh_token, tokens.expires_in);
    } catch(err) {
      setClients(prev => prev.map(c => c.id===clientId ? {...c,status:"disconnected"} : c));
      setError(err.message);
    }
  }, [scheduleRefresh]);

  const disconnectClient = (clientId) => {
    if (refreshTimers.current[clientId]) clearTimeout(refreshTimers.current[clientId]);
    setClients(prev => prev.map(c => c.id===clientId ? {...c,accessToken:null,refreshToken:null,status:"disconnected"} : c));
    if (activeId===clientId) setReport(null);
  };

  const removeClient = (clientId) => {
    if (refreshTimers.current[clientId]) clearTimeout(refreshTimers.current[clientId]);
    setClients(prev => prev.filter(c => c.id!==clientId));
    if (activeId===clientId) { setActiveId(null); setReport(null); }
  };

  const addClient = () => {
    if (!newName.trim()) return;
    const id = `c_${Date.now()}`;
    setClients(prev => [...prev, { id, name:newName.trim(), realmId:newRealm.trim(), status:"disconnected", accessToken:null, refreshToken:null }]);
    setNewName(""); setNewRealm(""); setActiveId(id); setView("dashboard");
  };

  const runReport = async () => {
    if (!activeClient?.accessToken) return;
    setLoading(true); setError(""); setReport(null);
    const def = REPORTS.find(r => r.value===reportType);
    try {
      const data = await qboReport(activeClient.accessToken, activeClient.realmId, reportType, def?.params(dateFrom,dateTo));
      setReport(data); setView("report");
    } catch(err) {
      if (err.message==="TOKEN_EXPIRED") {
        setClients(prev => prev.map(c => c.id===activeId ? {...c,status:"expired"} : c));
        setError("Access token expired. Click Reconnect.");
      } else setError(err.message);
    } finally { setLoading(false); }
  };

  const label = (text) => (
    <label style={{display:"block",fontSize:11,fontWeight:700,color:T.slate,marginBottom:4,textTransform:"uppercase",letterSpacing:".6px"}}>{text}</label>
  );

  return (
    <div style={{minHeight:"100vh",background:T.mist,color:T.text}}>

      {/* Top bar */}
      <div style={{background:T.navyDk,padding:"0 24px",display:"flex",alignItems:"center",justifyContent:"space-between",height:54,borderBottom:"1px solid rgba(255,255,255,.06)"}}>
        <div style={{display:"flex",alignItems:"center",gap:10}}>
          <div style={{width:28,height:28,borderRadius:7,background:T.green,display:"flex",alignItems:"center",justifyContent:"center"}}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none">
              <rect x="2" y="2" width="5" height="5" rx="1.2" fill="white" opacity=".9"/>
              <rect x="9" y="2" width="5" height="5" rx="1.2" fill="white" opacity=".5"/>
              <rect x="2" y="9" width="5" height="5" rx="1.2" fill="white" opacity=".5"/>
              <rect x="9" y="9" width="5" height="5" rx="1.2" fill="white" opacity=".9"/>
            </svg>
          </div>
          <span style={{color:T.white,fontWeight:700,fontSize:14}}>Clarity QBO Portal</span>
          <span style={{color:"#4A6580",fontSize:13}}>· Accountant Client Manager</span>
        </div>
        <div style={{display:"flex",gap:8,alignItems:"center"}}>
          <span style={{fontSize:12,color:"#4A6580"}}>{clients.filter(c=>c.status==="connected").length} connected</span>
          <button onClick={()=>setShowSupport(true)} title="Support" style={{width:28,height:28,borderRadius:"50%",background:"rgba(255,255,255,.08)",border:"1px solid rgba(255,255,255,.12)",color:"#8AA8C0",fontSize:14,fontWeight:700,cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center"}}>?</button>
          <Btn small onClick={()=>setView("addClient")}>Add Client</Btn>
        </div>
      </div>

      {/* Body */}
      <div style={{display:"flex",maxWidth:1080,margin:"0 auto",padding:"20px 16px",gap:18,alignItems:"flex-start"}}>

        {/* Sidebar */}
        <div style={{width:220,flexShrink:0}}>
          <div style={{fontSize:10,fontWeight:700,color:T.sub,textTransform:"uppercase",letterSpacing:".8px",marginBottom:8,paddingLeft:4}}>
            Clients ({clients.length})
          </div>
          {clients.length===0 && <div style={{fontSize:13,color:T.sub,padding:"12px 8px",lineHeight:1.6}}>No clients yet.<br/>Add one to get started.</div>}
          <div style={{display:"flex",flexDirection:"column",gap:6}}>
            {clients.map(c => (
              <div key={c.id} onClick={()=>{setActiveId(c.id);setView("dashboard");setReport(null);setError("");}}
                style={{padding:"11px 13px",borderRadius:9,cursor:"pointer",
                  background:activeId===c.id?T.white:"transparent",
                  border:activeId===c.id?`1.5px solid ${T.green}`:"1px solid transparent",
                  boxShadow:activeId===c.id?"0 1px 6px rgba(0,0,0,.07)":"none"}}>
                <div style={{fontWeight:600,fontSize:13,color:T.navy,marginBottom:5,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{c.name}</div>
                <Badge status={c.status}/>
              </div>
            ))}
          </div>
          <div style={{marginTop:12}}>
            <Btn small variant="secondary" onClick={()=>setView("addClient")} fullWidth>Add Client</Btn>
          </div>
        </div>

        {/* Main */}
        <div style={{flex:1,minWidth:0}}>

          {/* Error */}
          {error && (
            <div style={{background:T.errorBg,border:`1px solid ${T.error}`,borderRadius:8,padding:"11px 14px",fontSize:13,color:T.error,marginBottom:14,display:"flex",justifyContent:"space-between",alignItems:"center"}}>
              <span>⚠ {error}</span>
              <span onClick={()=>setError("")} style={{cursor:"pointer",fontWeight:700,marginLeft:12}}>✕</span>
            </div>
          )}

          {/* Add client */}
          {view==="addClient" && (
            <Card>
              <div style={{fontSize:16,fontWeight:700,color:T.navy,marginBottom:4}}>Add Client Company</div>
              <div style={{fontSize:13,color:T.sub,marginBottom:18}}>After adding, click Connect to QBO to authorize via QuickBooks login.</div>
              <div style={{marginBottom:12}}>
                {label("Client / Company Name")}
                <input type="text" value={newName} onChange={e=>setNewName(e.target.value)} placeholder="e.g. Acme Roofing LLC"
                  style={{width:"100%",padding:"9px 12px",border:`1px solid ${T.border}`,borderRadius:7,fontSize:14}}/>
              </div>
              <div style={{marginBottom:18}}>
                {label("QBO Realm ID (optional)")}
                <input type="text" value={newRealm} onChange={e=>setNewRealm(e.target.value)} placeholder="Auto-filled after OAuth"
                  style={{width:"100%",padding:"9px 12px",border:`1px solid ${T.border}`,borderRadius:7,fontSize:14}}/>
                <div style={{fontSize:11,color:T.sub,marginTop:4}}>Found in QBO URL after ?companyId= — leave blank to auto-fill.</div>
              </div>
              <div style={{display:"flex",gap:10}}>
                <Btn onClick={addClient} disabled={!newName.trim()}>Add Client</Btn>
                <Btn variant="secondary" onClick={()=>setView("dashboard")}>Cancel</Btn>
              </div>
            </Card>
          )}

          {/* Client dashboard */}
          {(view==="dashboard"||view==="report") && activeClient && (
            <>
              <Card style={{marginBottom:14}}>
                <div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start"}}>
                  <div>
                    <div style={{fontSize:18,fontWeight:700,color:T.navy}}>{activeClient.name}</div>
                    {activeClient.realmId && <div style={{fontSize:11,color:T.sub,marginTop:2}}>Realm ID: {activeClient.realmId}</div>}
                    {activeClient.tokenExpiry && activeClient.status==="connected" && (
                      <div style={{fontSize:11,color:T.sub,marginTop:1}}>Token expires: {new Date(activeClient.tokenExpiry).toLocaleTimeString()}</div>
                    )}
                    <div style={{marginTop:8}}><Badge status={activeClient.status}/></div>
                  </div>
                  <div style={{display:"flex",gap:8,flexWrap:"wrap",justifyContent:"flex-end"}}>
                    {activeClient.status!=="connected" && (
                      <Btn onClick={()=>connectClient(activeClient.id)} disabled={activeClient.status==="pending"}>
                        {activeClient.status==="pending" ? <><Spinner/> Connecting…</> : activeClient.status==="expired" ? "Reconnect" : "Connect to QBO"}
                      </Btn>
                    )}
                    {activeClient.status==="connected" && (
                      <Btn variant="secondary" small onClick={()=>disconnectClient(activeClient.id)}>Disconnect</Btn>
                    )}
                    <Btn variant="danger" small onClick={()=>removeClient(activeClient.id)}>Remove</Btn>
                  </div>
                </div>
              </Card>

              {activeClient.status==="connected" && (
                <Card>
                  <div style={{fontSize:14,fontWeight:700,color:T.navy,marginBottom:14}}>Run a Report</div>
                  <div style={{display:"flex",gap:10,flexWrap:"wrap",alignItems:"flex-end"}}>
                    <div>
                      {label("Report Type")}
                      <select value={reportType} onChange={e=>{setReportType(e.target.value);setReport(null);}}
                        style={{padding:"8px 12px",border:`1px solid ${T.border}`,borderRadius:7,fontSize:13,minWidth:210,background:T.white}}>
                        {REPORTS.map(r => <option key={r.value} value={r.value}>{r.label}</option>)}
                      </select>
                    </div>
                    <div>
                      {label("From")}
                      <input type="date" value={dateFrom} onChange={e=>setDateFrom(e.target.value)}
                        style={{padding:"8px 10px",border:`1px solid ${T.border}`,borderRadius:7,fontSize:13}}/>
                    </div>
                    <div>
                      {label("To")}
                      <input type="date" value={dateTo} onChange={e=>setDateTo(e.target.value)}
                        style={{padding:"8px 10px",border:`1px solid ${T.border}`,borderRadius:7,fontSize:13}}/>
                    </div>
                    <Btn onClick={runReport} disabled={loading}>
                      {loading ? <><Spinner/> Loading…</> : "Run Report"}
                    </Btn>
                  </div>
                  {view==="report" && report && (
                    <div style={{marginTop:20}}>
                      <div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start",marginBottom:4}}>
                        <div>
                          <div style={{fontSize:16,fontWeight:700,color:T.navy}}>{report.Header?.ReportName}</div>
                          <div style={{fontSize:12,color:T.sub,marginTop:2}}>
                            {report.Header?.StartPeriod ? `${report.Header.StartPeriod} → ${report.Header.EndPeriod}` : report.Header?.ReportDate||""}
                            {report.Header?.Currency ? ` · ${report.Header.Currency}` : ""}
                            {report._intuit_tid && <span style={{marginLeft:8,opacity:.5,fontSize:10}}>tid: {report._intuit_tid}</span>}
                          </div>
                        </div>
                        <Badge status="connected"/>
                      </div>
                      <ReportTable report={report}/>
                    </div>
                  )}
                </Card>
              )}

              {activeClient.status!=="connected" && (
                <Card style={{textAlign:"center",padding:44}}>
                  <div style={{fontSize:36,marginBottom:10}}>🔌</div>
                  <div style={{fontSize:15,fontWeight:700,color:T.navy,marginBottom:8}}>Connect {activeClient.name} to QuickBooks</div>
                  <div style={{fontSize:13,color:T.sub,maxWidth:360,margin:"0 auto 20px",lineHeight:1.7}}>
                    A QuickBooks login window will open. Sign in with this client's credentials or your accountant login.
                  </div>
                  <Btn onClick={()=>connectClient(activeClient.id)} disabled={activeClient.status==="pending"}>
                    {activeClient.status==="pending" ? <><Spinner/> Opening QuickBooks…</> : "Connect to QuickBooks"}
                  </Btn>
                </Card>
              )}
            </>
          )}

          {/* Empty state */}
          {!activeClient && view!=="addClient" && (
            <Card style={{textAlign:"center",padding:52}}>
              <div style={{fontSize:32,marginBottom:10}}>📋</div>
              <div style={{fontSize:16,fontWeight:700,color:T.navy,marginBottom:8}}>No client selected</div>
              <div style={{fontSize:13,color:T.sub,marginBottom:20}}>Add your first client company to begin accessing their QuickBooks data.</div>
              <Btn onClick={()=>setView("addClient")}>Add First Client</Btn>
            </Card>
          )}
        </div>
      </div>

      {showSupport && <SupportPanel onClose={()=>setShowSupport(false)}/>}

      {/* Footer */}
      <div style={{textAlign:"center",padding:"12px 20px 28px",fontSize:11,color:"#8A9BB0",lineHeight:2}}>
        Clarity Business Books & Accounting · Tokens held in session memory only · Built on QuickBooks API · Not affiliated with Intuit<br/>
        Need help?{" "}
        <a href={`mailto:${CONFIG.SUPPORT.email}?subject=Clarity QBO Portal Support`} style={{color:T.greenLt,textDecoration:"none"}}>{CONFIG.SUPPORT.email}</a>
        {" · "}
        <span onClick={()=>setShowSupport(true)} style={{cursor:"pointer",color:T.greenLt,textDecoration:"underline"}}>Contact Support</span>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
</body>
</html>