// Products, Stocking Keys, Balance, Loaders — wired to the API.

function ProductsPage({ isAdmin, me, onChange }) {
  const { data: products, refresh } = useApi('/api/products');
  const { data: batches, refresh: refreshBatches } = useApi('/api/batches');
  const [filter, setFilter] = React.useState("All");
  const [editing, setEditing] = React.useState(null);  // product or {} for new
  const [genFor, setGenFor] = React.useState(null);
  const [buyAccessFor, setBuyAccessFor] = React.useState(null);
  const [reveal, setReveal] = React.useState(null);

  const list = products || [];
  const games = ["All", ...Array.from(new Set(list.map(p => p.game).filter(Boolean)))];
  const filtered = filter === "All" ? list : list.filter(p => p.game === filter);

  const viewBatch = async (batch) => {
    try {
      const keys = await api.get('/api/batches/'+batch.id+'/keys');
      setReveal({ batchId: batch.id, keys: keys.map(k => k.key_value), productName: batch.product_name, duration: batch.duration });
    } catch (e) { toast(e.message,'err'); }
  };
  const downloadBatch = async (batchId) => {
    try {
      const keys = await api.get('/api/batches/'+batchId+'/keys');
      const csv = keys.map(k => k.key_value).join('\n');
      const blob = new Blob([csv],{type:'text/csv'});
      const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `keys-${batchId}.csv`; a.click();
      URL.revokeObjectURL(a.href);
    } catch (e) { toast(e.message,'err'); }
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>Products</h1>
          <p>{isAdmin ? "Your catalog." : "Pick a product, unlock the panel, generate keys."}</p>
        </div>
        {isAdmin && <button className="btn btn-primary" onClick={()=>setEditing({})}><Ico.Plus className="icon"/> Add product</button>}
      </div>

      <div className="flex gap-8" style={{ flexWrap: 'wrap', marginBottom: 16 }}>
        {games.map(g => (
          <span key={g} className={`pill ${filter === g ? 'gold' : ''}`} style={{ cursor: 'pointer', padding: '6px 14px', fontSize: 12 }} onClick={() => setFilter(g)}>{g}</span>
        ))}
      </div>

      {(!list || !list.length) && (
        <div className="card"><EmptyState
          icon={<Ico.Box/>}
          title={isAdmin?'No products yet':'No products available'}
          desc={isAdmin?'Add your first product to make it generatable by resellers. Set the License backend App ID so keys mint under the right worker app.':'Check back later — the admin hasn’t added any products yet.'}
          action={isAdmin && <button className="btn btn-primary" onClick={()=>setEditing({})}><Ico.Plus className="icon"/> Add your first product</button>}
        /></div>
      )}

      <div className="grid grid-3">
        {filtered.map(p => (
          <div key={p.id} className="card" style={{ position: 'relative', overflow: 'hidden' }}>
            <div style={{
              height: 140,
              background: p.has_image
                ? `#000 url(/img/products/${p.id}) center/cover no-repeat`
                : `linear-gradient(135deg, color-mix(in oklab, var(--accent) 12%, transparent), color-mix(in oklab, var(--accent) 3%, transparent))`,
              position: 'relative', overflow: 'hidden', borderBottom: '1px solid var(--border)',
            }}>
              {!p.has_image && (
                <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }}>
                  <div style={{
                    fontFamily: 'JetBrains Mono, monospace', fontSize: 12, color: 'var(--accent)',
                    fontWeight: 600, letterSpacing: '0.1em', textShadow: '0 0 12px var(--accent-glow)',
                    padding: '6px 14px', border: '1px solid color-mix(in oklab, var(--accent) 40%, transparent)',
                    borderRadius: 4, background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)',
                  }}>{(p.game||p.name||'PRODUCT').toUpperCase()}</div>
                </div>
              )}
              <div style={{ position: 'absolute', top: 10, right: 10, display: 'flex', gap: 6 }}>
                {p.status === 'live' && <span className="pill green"><span className="dot"/>live</span>}
                {p.status === 'restock' && <span className="pill amber"><span className="dot"/>restock</span>}
                {p.status === 'updating' && <span className="pill red"><span className="dot"/>updating</span>}
                {p.status === 'offline' && <span className="pill red"><span className="dot"/>offline</span>}
                {p.status === 'archived' && <span className="pill" style={{ background: 'var(--bg-card-2)', color: 'var(--text-dimmer)' }}>archived</span>}
              </div>
            </div>

            <div className="card-pad">
              <div className="flex-between" style={{ alignItems: 'flex-start' }}>
                <div>
                  <div style={{ fontWeight: 800, fontSize: 16, letterSpacing: '-0.01em' }}>{p.name}</div>
                  <div className="mono" style={{ fontSize: 11, color: 'var(--text-dimmer)', marginTop: 2 }}>
                    PRD-{String(p.id).padStart(4,'0')}{p.app_id && <> · <span style={{ color: 'var(--accent)' }}>{p.app_id}</span></>}
                  </div>
                </div>
                <div style={{ textAlign: 'right' }}>
                  {(() => {
                    const minPrice = (p.durations||[]).length ? Math.min(...p.durations.map(d => d.price)) : 0;
                    return <>
                      <div className="mono" style={{ fontWeight: 800, fontSize: 18, color: 'var(--accent)' }}>{minPrice > 0 ? `${fmtMoney(minPrice)}+` : 'free'}</div>
                      <div style={{ fontSize: 10.5, color: 'var(--text-dimmer)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>/ key</div>
                    </>;
                  })()}
                </div>
              </div>

              <div className="flex gap-8" style={{ flexWrap: 'wrap' }}>
                {(p.durations||[]).map(d => (
                  <span key={d.label} className="tag" title={`${d.days}d · ${fmtMoney(d.price)}/key`}>
                    {d.label} · {fmtMoney(d.price)}
                  </span>
                ))}
              </div>

              <div className="flex-between mt-16" style={{ fontSize: 12, color: 'var(--text-dim)' }}>
                <span>{(p.durations||[]).length} duration{(p.durations||[]).length===1?'':'s'}</span>
                {p.panel_price_monthly > 0 && <span>Panel: <b className="mono" style={{ color: 'var(--accent)' }}>{fmtMoney(p.panel_price_monthly)}/mo</b></span>}
              </div>

              {!isAdmin && p.has_access && (
                <div style={{
                  marginTop: 12, padding: '8px 10px', borderRadius: 'var(--r-md)', fontSize: 11.5,
                  background: 'rgba(15,168,95,0.08)', border: '1px solid rgba(15,168,95,0.3)',
                  color: 'var(--success)', display: 'flex', alignItems: 'center', gap: 6,
                }}>
                  <Ico.Check width={11} height={11}/>
                  {p.access?.lifetime ? 'Lifetime panel — unlimited free keys' : `Panel active · ${p.access?.days_left||0}d left · unlimited free keys`}
                </div>
              )}

              <div className="flex gap-8 mt-16">
                {isAdmin ? (
                  <>
                    <button className="btn btn-sm" style={{ flex: 1 }} onClick={()=>setEditing(p)}><Ico.Edit className="icon" width={12} height={12}/> Edit</button>
                    <button className="btn btn-sm" onClick={async () => {
                      if (!confirm(`Archive ${p.name}? It will disappear from the catalogue but existing keys stay valid.`)) return;
                      await api.del('/api/products/'+p.id); toast('Archived'); refresh();
                    }} style={{ color: 'var(--danger)' }} title="Archive (soft-delete)"><Ico.Trash className="icon" width={12} height={12}/></button>
                  </>
                ) : (
                  <>
                    <button className="btn btn-primary btn-sm" style={{ flex: 1 }} disabled={p.status !== 'live'} onClick={()=>setGenFor(p)}>
                      <Ico.Key className="icon" width={12} height={12}/> {p.has_access ? 'Generate' : 'Buy keys'}
                    </button>
                    {p.panel_price_monthly > 0 && !p.has_access && (
                      <button className="btn btn-sm" onClick={()=>setBuyAccessFor(p)} title="Buy unlimited monthly access"><Ico.Lock className="icon" width={12} height={12}/></button>
                    )}
                  </>
                )}
              </div>
            </div>
          </div>
        ))}
      </div>

      {(batches||[]).length > 0 && (
        <div className="card mt-24">
          <div className="card-head">
            <h3>{isAdmin ? 'Recent orders' : 'Your recent orders'}</h3>
            <button className="btn btn-sm btn-ghost" onClick={refreshBatches}><Ico.Refresh className="icon" width={12} height={12}/></button>
          </div>
          <table className="table">
            <thead><tr><th>#</th>{isAdmin && <th>Reseller</th>}<th>Product</th><th>Duration</th><th>Qty</th><th>Cost</th><th>When</th><th></th></tr></thead>
            <tbody>
              {batches.slice(0, 8).map(b => (
                <tr key={b.id}>
                  <td className="mono" style={{ fontSize: 12, fontWeight: 600 }}>#{String(b.id).padStart(5,'0')}</td>
                  {isAdmin && <td>{b.reseller||'—'}</td>}
                  <td>{b.product_name}</td>
                  <td><span className="tag">{b.duration}</span></td>
                  <td className="mono">{b.quantity}</td>
                  <td className="mono" style={{ color: b.cost>0?'var(--accent)':'var(--text-dimmer)', fontWeight: 600 }}>{b.cost>0?fmtMoney(b.cost):'free'}</td>
                  <td style={{ color: 'var(--text-dim)', fontSize: 12 }}>{fmtTime(b.created_at)}</td>
                  <td>
                    <div className="flex gap-4">
                      <button className="btn btn-sm btn-primary" onClick={()=>viewBatch(b)} title="Show / copy keys"><Ico.Eye className="icon" width={12} height={12}/> Keys</button>
                      <button className="btn btn-sm btn-ghost" onClick={()=>downloadBatch(b.id)} title="Download CSV"><Ico.Down className="icon" width={12} height={12}/></button>
                    </div>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {editing && <ProductEditModal product={editing} onClose={()=>setEditing(null)} onSaved={()=>{ setEditing(null); refresh(); }}/>}
      {genFor && <GenerateModal product={genFor} me={me} onClose={()=>setGenFor(null)} onDone={()=>{ setGenFor(null); refresh(); refreshBatches(); onChange&&onChange(); }}/>}
      {buyAccessFor && <BuyAccessModal product={buyAccessFor} me={me} onClose={()=>setBuyAccessFor(null)} onDone={()=>{ setBuyAccessFor(null); refresh(); onChange&&onChange(); }}/>}
      {reveal && <KeysReveal open keys={reveal.keys} batchId={reveal.batchId} productName={reveal.productName} duration={reveal.duration} onClose={()=>setReveal(null)}/>}
    </div>
  );
}

function ProductEditModal({ product, onClose, onSaved }) {
  const isNew = !product.id;
  const initialDurations = Array.isArray(product.durations) && product.durations.length
    ? product.durations.map(d => ({ label: d.label, days: d.days, price: d.price ?? 0 }))
    : [
        { label: "1 Day", days: 1, price: 1 },
        { label: "1 Week", days: 7, price: 5 },
        { label: "1 Month", days: 30, price: 15 },
        { label: "Lifetime", days: 36500, price: 99 },
      ];
  const [f, setF] = React.useState({
    name: product.name||"", game: product.game||"", app_id: product.app_id||"",
    panel_price_monthly: product.panel_price_monthly ?? 0,
    status: product.status||"live",
    description: product.description||"",
    durations: initialDurations,
  });
  const [busy, setBusy] = React.useState(false);
  const [imageFile, setImageFile] = React.useState(null);
  const [imageVer, setImageVer] = React.useState(0);   // bump to bust browser cache after upload
  const setD = (i, key, val) => {
    const next = f.durations.slice();
    next[i] = { ...next[i], [key]: val };
    setF({...f, durations: next});
  };
  const addD = () => setF({...f, durations: [...f.durations, { label: "", days: 30, price: 0 }]});
  const rmD = (i) => setF({...f, durations: f.durations.filter((_,j)=>j!==i)});
  const save = async () => {
    if (!f.name.trim()) return toast("Name is required","warn");
    if (!f.durations.length) return toast("Add at least one duration","warn");
    if (f.durations.some(d => !d.label.trim() || !(+d.days >= 1) || !(+d.price >= 0))) return toast("Every duration needs label, days≥1, price≥0","warn");
    setBusy(true);
    try {
      const payload = { ...f, durations: f.durations.map(d => ({ label: d.label.trim(), days: +d.days, price: +d.price })) };
      let pid = product.id;
      if (isNew) { const r = await api.post("/api/products", payload); pid = r.id; }
      else await api.patch("/api/products/"+product.id, payload);
      if (imageFile && pid) {
        try { await api.upload(`/api/products/${pid}/image`, imageFile); }
        catch (e) { toast('Saved, but image upload failed: '+e.message,'warn'); }
      }
      toast(isNew ? "Product created" : "Product saved");
      onSaved();
    } catch (e) { toast(e.message, "err"); }
    setBusy(false);
  };
  const removeImage = async () => {
    if (!product.id) return;
    try { await api.del(`/api/products/${product.id}/image`); setImageFile(null); setImageVer(v=>v+1); toast('Image removed'); }
    catch (e) { toast(e.message,'err'); }
  };
  return (
    <Modal open onClose={onClose} title={isNew ? "Add product" : ("Edit " + product.name)} width={580}>
      <div className="flex-col gap-12">
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Name *</label><input className="input" value={f.name} onChange={e=>setF({...f,name:e.target.value})} placeholder="Product name"/></div>
          <div className="field"><label>Game</label><input className="input" value={f.game} onChange={e=>setF({...f,game:e.target.value})} placeholder="Game name"/></div>
        </div>
        <div className="field">
          <label>License backend App ID</label>
          <input className="input mono" value={f.app_id} onChange={e=>setF({...f,app_id:e.target.value})} placeholder="worker app_id (blank = prd-N)"/>
        </div>
        <div className="field">
          <label>Panel price (per month, $)</label>
          <NumberInput value={f.panel_price_monthly} onChange={v=>setF({...f,panel_price_monthly:v})} min={0} step={1} prefix="$" presets={[0,5,10,20,30,50,100]}/>
          <small style={{ display:"block", marginTop: 4, fontSize: 11, color:"var(--text-dimmer)" }}>
            Optional monthly subscription. Owners generate unlimited keys for free. Set <b>$0</b> to disable the subscription path (admin-grant only).
          </small>
        </div>
        <div className="field">
          <div className="flex-between" style={{ marginBottom: 6 }}>
            <label>Durations & pay-per-key price</label>
            <button type="button" className="btn btn-sm" onClick={addD}><Ico.Plus className="icon" width={12} height={12}/> Add</button>
          </div>
          <div style={{ border: "1px solid var(--border)", borderRadius: "var(--r-md)", overflow: "hidden" }}>
            <div style={{ display:"grid", gridTemplateColumns: "2fr 1fr 1fr 40px", gap: 8, padding: "8px 10px", background:"var(--bg-card-2)", fontSize: 11, color:"var(--text-dimmer)", textTransform:"uppercase", letterSpacing:"0.05em", fontWeight: 600 }}>
              <div>Label</div><div>Days</div><div>$ / key</div><div></div>
            </div>
            {f.durations.map((d,i) => (
              <div key={i} style={{ display:"grid", gridTemplateColumns: "2fr 1fr 1fr 40px", gap: 8, padding: "6px 10px", borderTop: "1px solid var(--border)" }}>
                <input className="input" placeholder="e.g. 1 Month" value={d.label} onChange={e=>setD(i,"label",e.target.value)}/>
                <input className="input mono" type="number" min="1" value={d.days} onChange={e=>setD(i,"days",+e.target.value||1)}/>
                <input className="input mono" type="number" min="0" step="0.01" value={d.price} onChange={e=>setD(i,"price",+e.target.value||0)}/>
                <button type="button" className="btn btn-sm btn-ghost" onClick={()=>rmD(i)} style={{ color:"var(--danger)" }} title="Remove"><Ico.Trash width={12} height={12} className="icon"/></button>
              </div>
            ))}
          </div>
          <small style={{ display:"block", marginTop: 4, fontSize: 11, color:"var(--text-dimmer)" }}>
            Users without a panel pay this much per key (charged from balance). Panel owners pay nothing.
          </small>
        </div>
        <div className="field"><label>Status</label>
          <select className="select" value={f.status} onChange={e=>setF({...f,status:e.target.value})}>
            <option value="live">live</option><option value="restock">restock</option>
            <option value="updating">updating</option><option value="offline">offline</option>
            <option value="archived">archived</option>
          </select>
        </div>

        <div className="field">
          <label>Product image (optional)</label>
          <div className="flex gap-12" style={{ alignItems: 'center' }}>
            <div style={{
              width: 96, height: 60, borderRadius: 6, flexShrink: 0,
              border: '1px solid var(--border)', overflow: 'hidden',
              background: (imageFile ? `#000 url(${URL.createObjectURL(imageFile)}) center/cover no-repeat`
                                     : (product.id ? `#000 url(/img/products/${product.id}?v=${imageVer}) center/cover no-repeat` : 'var(--bg-card-2)')),
              display: 'grid', placeItems: 'center', color: 'var(--text-dimmer)', fontSize: 11,
            }}>
              {!imageFile && !product.id && '128 × 80'}
            </div>
            <div style={{ flex: 1 }}>
              <input type="file" accept="image/png,image/jpeg,image/webp,image/gif" onChange={e=>setImageFile(e.target.files[0]||null)}/>
              <small style={{ display: 'block', marginTop: 4, fontSize: 11, color: 'var(--text-dimmer)' }}>PNG / JPG / WEBP / GIF, up to 4 MB. 16:10 looks best on the card.</small>
            </div>
            {product.id && <button type="button" className="btn btn-sm" onClick={removeImage} style={{ color: 'var(--danger)' }}>Remove</button>}
          </div>
        </div>

        <div className="field"><label>Description</label><textarea className="textarea" rows="3" value={f.description} onChange={e=>setF({...f,description:e.target.value})}/></div>
        <div className="flex gap-8" style={{ justifyContent: "flex-end" }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={save}>{busy?"Saving...":(isNew?"Create product":"Save changes")}</button>
        </div>
      </div>
    </Modal>
  );
}

function BuyAccessModal({ product, me, onClose, onDone }) {
  const [months, setMonths] = React.useState(1);
  const [busy, setBusy] = React.useState(false);
  const cost = +(product.panel_price_monthly * months).toFixed(2);
  const insufficient = (me?.balance||0) < cost;
  const submit = async () => {
    setBusy(true);
    try {
      await api.post(`/api/products/${product.id}/access/buy`, { months });
      toast(`Unlocked ${product.name} for ${months} month${months>1?'s':''}`);
      onDone();
    } catch (e) { toast(e.message,'err'); }
    setBusy(false);
  };
  return (
    <Modal open onClose={onClose} title={`Unlock ${product.name}`} width={460}>
      <div className="flex-col gap-16">
        <div style={{ padding: 14, background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <Ico.Lock width={28} height={28} style={{ color: 'var(--accent)' }}/>
          <div style={{ flex: 1 }}>
            <div style={{ fontWeight: 700, fontSize: 13 }}>{product.name}</div>
            <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>Product access — {fmtMoney(product.panel_price_monthly)}/mo</div>
          </div>
        </div>
        <div className="field">
          <label>Months</label>
          <NumberInput value={months} onChange={setMonths} min={1} max={24} step={1} suffix="mo" presets={[1,3,6,12]}/>
        </div>
        <div className="divider"/>
        <div className="flex-between">
          <div>
            <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>{months} × {fmtMoney(product.panel_price_monthly)}</div>
            <div className="mono" style={{ fontSize: 24, fontWeight: 800, color: 'var(--accent)' }}>{fmtMoney(cost)}</div>
            <div style={{ fontSize: 11, color: insufficient?'var(--danger)':'var(--text-dim)' }}>Balance {fmtMoney(me?.balance||0)} {insufficient && '— top up first'}</div>
          </div>
          <button className="btn btn-primary btn-lg" disabled={busy||insufficient} onClick={submit}><Ico.Zap className="icon"/> {busy?'Processing…':'Unlock'}</button>
        </div>
      </div>
    </Modal>
  );
}

function GenerateModal({ product, me, onClose, onDone }) {
  const durations = Array.isArray(product.durations) && product.durations.length ? product.durations : [{ label:'1 Month', days:30, price: 0 }];
  const prefixKey = `evip.prefix.p${product.id}`;
  const savedPrefix = (() => { try { return localStorage.getItem(prefixKey) || ''; } catch { return ''; } })();
  const [duration, setDuration] = React.useState(durations[0].label);
  const [qty, setQty] = React.useState(1);
  const [prefix, setPrefix] = React.useState(savedPrefix);
  const [result, setResult] = React.useState(null);
  const [busy, setBusy] = React.useState(false);

  const isAdmin = me?.role === 'admin';
  const panelActive = product.has_access || isAdmin;
  const chosen = durations.find(d => d.label === duration) || durations[0];
  const unitPrice = panelActive ? 0 : (+chosen.price||0);
  const cost = +(unitPrice * qty).toFixed(2);
  const insufficient = !panelActive && (me?.balance||0) < cost;
  const prefixUpper = (prefix||'').toUpperCase();
  const prefixValid = !prefixUpper || /^[A-Z0-9]{1,8}$/.test(prefixUpper);

  const submit = async () => {
    if (!prefixValid) return toast('Prefix: 1-8 letters or digits', 'warn');
    setBusy(true);
    try {
      const r = await api.post('/api/keys/generate', { product_id: product.id, duration, quantity: qty, prefix: prefixUpper || undefined });
      try { if (prefixUpper) localStorage.setItem(prefixKey, prefixUpper); else localStorage.removeItem(prefixKey); } catch {}
      setResult(r);
    } catch (e) { toast(e.message, 'err'); }
    setBusy(false);
  };
  if (result) {
    return <KeysReveal open keys={result.keys} batchId={result.batchId} cost={result.cost} balanceAfter={result.balance_after} productName={product.name} duration={duration} onClose={()=>{ setResult(null); onDone(); }}/>;
  }
  return (
    <Modal open onClose={onClose} title={`Generate keys — ${product.name}`} width={500} closeOnBackdrop={false}>
      <div className="flex-col gap-16">
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Duration</label>
            <select className="select" value={duration} onChange={e=>setDuration(e.target.value)}>
              {durations.map(d=><option key={d.label} value={d.label}>{d.label} · {fmtMoney(d.price)}/key</option>)}
            </select>
          </div>
          <div className="field"><label>Quantity</label>
            <NumberInput value={qty} onChange={setQty} min={1} max={500} step={1} presets={[1,5,10,25,100]}/>
          </div>
        </div>

        <div className="field">
          <label>Key prefix <span style={{ color: 'var(--text-dimmer)', fontWeight: 400 }}>(optional, remembered per product)</span></label>
          <input
            className="input mono"
            value={prefix}
            onChange={e=>setPrefix(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g,'').slice(0,8))}
            onKeyDown={e=>{
              if (e.key === 'Enter') {
                e.preventDefault();
                e.stopPropagation();
                if (!busy && !insufficient) submit();
              }
            }}
            placeholder="e.g. TEMP"
            maxLength={8}
            style={{ borderColor: prefix && !prefixValid ? 'var(--danger)' : undefined }}
          />
          <small style={{ display:'block', marginTop: 4, fontSize: 11, color:'var(--text-dimmer)' }}>
            Keys will look like <span className="mono" style={{ color:'var(--accent)' }}>{(prefixUpper||'XX')}-XXXXXXXXXXXXXXXX</span>. Up to 8 letters/digits.
          </small>
        </div>

        {panelActive ? (
          <div style={{ padding: 12, background: 'rgba(15,168,95,0.08)', border: '1px solid rgba(15,168,95,0.3)', borderRadius: 'var(--r-md)', fontSize: 13, color: 'var(--success)' }}>
            <Ico.Check width={14} height={14} style={{ display:'inline', marginRight: 6, verticalAlign: 'middle' }}/>
            <b>Free.</b> You own this product's panel — generate as many as you want.
          </div>
        ) : (
          <div style={{ padding: 12, background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)' }}>
            <div className="flex-between">
              <div>
                <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>{qty} × {fmtMoney(unitPrice)}</div>
                <div className="mono" style={{ fontSize: 22, fontWeight: 800, color: 'var(--accent)' }}>{fmtMoney(cost)}</div>
              </div>
              <div style={{ textAlign: 'right', fontSize: 11, color: insufficient?'var(--danger)':'var(--text-dim)' }}>
                Balance<br/><b className="mono" style={{ fontSize: 13 }}>{fmtMoney(me?.balance||0)}</b>
                {insufficient && <div style={{ marginTop: 4 }}>not enough</div>}
              </div>
            </div>
            {product.panel_price_monthly > 0 && (
              <div style={{ marginTop: 10, paddingTop: 10, borderTop: '1px solid var(--border)', fontSize: 11.5, color: 'var(--text-dim)' }}>
                Or buy the panel for <b style={{ color: 'var(--accent)' }}>{fmtMoney(product.panel_price_monthly)}/mo</b> and generate unlimited keys for free.
              </div>
            )}
          </div>
        )}

        <div className="flex-between">
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary btn-lg" disabled={busy || insufficient} onClick={submit}>
            <Ico.Zap className="icon"/> {busy?'Generating…':(panelActive?`Generate ${qty}`:`Buy ${qty} key${qty>1?'s':''} · ${fmtMoney(cost)}`)}
          </button>
        </div>
      </div>
    </Modal>
  );
}

function BalancePage({ me, onChange }) {
  const { data: bal, refresh: refreshBal } = useApi("/api/balance");
  const { data: txns, refresh: refreshTxns } = useApi("/api/balance/txns");
  const { data: methodInfo } = useApi("/api/balance/methods");
  const isAdmin = me?.role === "admin";
  const { data: pending, refresh: refreshPending } = useApi(isAdmin ? "/api/balance/pending" : null);
  const { data: railHealth, refresh: refreshRailHealth } = useApi(isAdmin ? "/api/balance/health" : null);

  const enabled = methodInfo?.methods || [];
  const supportedCoins = methodInfo?.supported || enabled.map(id => ({ id, configured: true, valid: true }));
  const [method, setMethod] = React.useState("");
  React.useEffect(()=>{
    if ((!method || !enabled.includes(method)) && enabled.length) setMethod(enabled[0]);
  }, [methodInfo, method]);
  const [amount, setAmount] = React.useState(50);
  const [busy, setBusy] = React.useState(false);
  const [activeTxn, setActiveTxn] = React.useState(null);

  const COIN_META = {
    BTC: { name: "Bitcoin",   sub: "BTC mainnet" },
    ETH: { name: "Ethereum",  sub: "ETH mainnet" },
    SOL: { name: "Solana",    sub: "SOL mainnet" },
    LTC: { name: "Litecoin",  sub: "LTC mainnet" },
  };

  const pendingMine = (txns||[]).filter(t => t.status === "pending");

  const createTopup = async () => {
    if (!method) return toast("No deposit methods configured", "err");
    setBusy(true);
    try {
      const r = await api.post("/api/balance/topup", { amount: +amount, method });
      setActiveTxn(r.id);
      toast("Top-up created");
      refreshTxns();
    } catch (e) { toast(e.message, "err"); }
    setBusy(false);
  };

  const approve = async (id, ok) => {
    try {
      await api.post(`/api/balance/${id}/${ok?"approve":"reject"}`);
      toast(ok?"Approved":"Rejected");
      refreshPending(); refreshBal(); refreshTxns(); onChange && onChange();
    } catch (e) { toast(e.message, "err"); }
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>Wallet</h1>
          <p>{isAdmin ? "Approve incoming crypto top-ups." : "Top up your balance with crypto."}</p>
        </div>
      </div>

      <div className="grid grid-3" style={{ marginBottom: 24 }}>
        <div className="card card-glow card-pad">
          <div className="eyebrow" style={{ color: "var(--text-dim)" }}>Balance</div>
          <div className="mono" style={{ fontSize: 40, fontWeight: 800, color: "var(--accent)", marginTop: 6, textShadow: "0 0 24px var(--accent-glow)" }}>{fmtMoney(bal?.balance||me?.balance||0)}</div>
        </div>
        <div className="card card-pad">
          <div className="eyebrow" style={{ color: "var(--text-dim)" }}>Deposited (all time)</div>
          <div className="mono" style={{ fontSize: 40, fontWeight: 800, marginTop: 6 }}>{fmtMoney(bal?.deposited||0)}</div>
        </div>
        <div className="card card-pad">
          <div className="eyebrow" style={{ color: "var(--text-dim)" }}>Spent (all time)</div>
          <div className="mono" style={{ fontSize: 40, fontWeight: 800, marginTop: 6 }}>{fmtMoney(bal?.spent||0)}</div>
        </div>
      </div>

      {isAdmin && (
        <div className="card" style={{ marginBottom: 24 }}>
          <div className="card-head"><h3>Pending top-ups</h3><span className="pill amber">{(pending||[]).length}</span></div>
          <table className="table">
            <thead><tr><th>When</th><th>User</th><th>Method</th><th>Send exactly</th><th>Credit</th><th>TX hash</th><th></th></tr></thead>
            <tbody>
              {(pending||[]).map(t => (
                <tr key={t.id}>
                  <td style={{ color: "var(--text-dim)" }}>{fmtTime(t.created_at)}</td>
                  <td>{t.username}</td>
                  <td><span className="tag">{t.method}</span></td>
                  <td className="mono" style={{ color: "var(--accent)", fontWeight: 700 }}>{(+t.amount).toFixed({BTC:8,LTC:8,ETH:6,SOL:6}[t.method]||8)} {t.method}</td>
                  <td className="mono">${(+(t.amount_requested ?? t.amount)).toFixed(2)}</td>
                  <td className="mono" style={{ fontSize: 11, color: t.txid ? "var(--text)" : "var(--text-dimmer)" }}>{t.txid ? (t.txid.slice(0,18)+(t.txid.length>18?"…":"")) : "—"}</td>
                  <td>
                    <div className="flex gap-4">
                      <button className="btn btn-sm" onClick={()=>setActiveTxn(t.id)} title="Chat / details"><Ico.Chat width={12} height={12} className="icon"/>{t.msg_count > 0 ? " "+t.msg_count : ""}</button>
                      <button className="btn btn-sm btn-primary" onClick={()=>approve(t.id,true)}>Approve</button>
                      <button className="btn btn-sm" onClick={()=>approve(t.id,false)} style={{ color: "var(--danger)" }}>Reject</button>
                    </div>
                  </td>
                </tr>
              ))}
              {(!pending || !pending.length) && <tr><td colSpan="7" style={{ textAlign: "center", color: "var(--text-dimmer)", padding: 24 }}>No pending top-ups.</td></tr>}
            </tbody>
          </table>
        </div>
      )}

      {isAdmin && (
        <div className="card" style={{ marginBottom: 24 }}>
          <div className="card-head"><h3>Deposit rails</h3><button className="btn btn-sm btn-ghost" onClick={refreshRailHealth}><Ico.Refresh width={12} height={12} className="icon"/></button></div>
          <table className="table">
            <thead><tr><th>Coin</th><th>Address</th><th>Price feed</th><th>Chain API</th><th>Status</th></tr></thead>
            <tbody>
              {(railHealth?.coins || supportedCoins).map(c => (
                <tr key={c.id}>
                  <td><span className="tag">{c.id}</span></td>
                  <td className="mono" style={{ fontSize: 11, color: c.configured && c.valid ? "var(--text-dim)" : "var(--danger)" }}>{c.configured ? (c.valid ? "configured" : "invalid") : "missing"}</td>
                  <td>{c.price_ok === undefined ? "..." : (c.price_ok ? <span className="pill green"><span className="dot"/>ok</span> : <span className="pill red"><span className="dot"/>fail</span>)}</td>
                  <td>{!c.configured || !c.valid ? <span className="pill">not checked</span> : (c.chain_ok ? <span className="pill green"><span className="dot"/>ok</span> : <span className="pill red"><span className="dot"/>fail</span>)}</td>
                  <td style={{ color: c.error ? "var(--danger)" : "var(--text-dimmer)", fontSize: 12 }}>{c.error || (c.configured && c.valid ? "ready" : "set .env address")}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {!isAdmin && pendingMine.length > 0 && (
        <div className="card" style={{ marginBottom: 24 }}>
          <div className="card-head"><h3>Your pending top-ups</h3><span className="pill amber">{pendingMine.length}</span></div>
          <div>
            {pendingMine.map(t => (
              <div key={t.id} style={{ padding: "14px 18px", borderTop: "1px solid var(--border)", display: "grid", gridTemplateColumns: "auto 1fr auto auto", gap: 16, alignItems: "center" }}>
                <div className="mono" style={{ fontSize: 11, color: "var(--text-dimmer)" }}>#{t.id}</div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 600 }}>Send exactly <b className="mono" style={{ color: "var(--accent)" }}>{(+t.amount).toFixed({BTC:8,LTC:8,ETH:6,SOL:6}[t.method]||8)} {t.method}</b></div>
                  <div style={{ fontSize: 11, color: "var(--text-dimmer)", marginTop: 2 }}>credits ${(+(t.amount_requested ?? t.amount)).toFixed(2)} once confirmed · created {fmtTime(t.created_at)}</div>
                </div>
                <span className="pill amber"><span className="dot"/>pending</span>
                <button className="btn btn-sm" onClick={()=>setActiveTxn(t.id)}><Ico.Chat width={12} height={12} className="icon"/> Open</button>
              </div>
            ))}
          </div>
        </div>
      )}

      {!isAdmin && (
      <div className="grid grid-1-2">
        <div className="card card-glow">
          <div className="card-head"><h3>New top-up</h3></div>
          <div className="card-pad flex-col gap-16">
            {!enabled.length && (
              <div style={{ padding: 12, background: "rgba(255,184,0,0.08)", border: "1px solid rgba(255,184,0,0.3)", borderRadius: "var(--r-md)", color: "#ffd17a", fontSize: 12.5 }}>
                Admin hasn't configured any deposit addresses yet.
              </div>
            )}
            <div className="field">
              <label>Coin</label>
              <div className="grid grid-2" style={{ gap: 8 }}>
                {supportedCoins.map(c => {
                  const id = c.id;
                  const active = c.configured && c.valid;
                  const m = COIN_META[id] || { name: id, sub: id };
                  return (
                    <div key={id} onClick={() => active && setMethod(id)} title={active ? `${m.name} deposits enabled` : `${m.name} address not configured`} style={{
                      padding: 14, background: "var(--bg-card-2)",
                      border: `1px solid ${method === id ? "var(--accent)" : "var(--border)"}`,
                      borderRadius: "var(--r-md)", cursor: active ? "pointer" : "not-allowed", opacity: active ? 1 : 0.55,
                      boxShadow: method === id ? "0 0 20px -6px var(--accent-glow)" : "none",
                    }}>
                      <div style={{ fontWeight: 700, fontSize: 13 }}>{m.name}</div>
                      <div style={{ fontSize: 11, color: "var(--text-dimmer)" }}>{active ? m.sub : "Admin setup needed"}</div>
                    </div>
                  );
                })}
              </div>
            </div>
            <div className="field">
              <label>Amount (USD)</label>
              <NumberInput value={amount} onChange={setAmount} min={1} step={5} prefix="$" presets={[25,50,100,250,500,1000]}/>
            </div>
            <button className="btn btn-primary btn-lg" disabled={busy || !enabled.length} onClick={createTopup} style={{ justifyContent: "center" }}>
              {busy ? "Creating…" : "Create top-up"}
            </button>
            <div style={{ fontSize: 11, color: "var(--text-dimmer)", lineHeight: 1.5 }}>
              You'll get an exact crypto amount to send (with a tiny unique tail so we can match your deposit on-chain). The system auto-approves you within a minute once it sees the exact amount land. Or just paste the TX hash and ping the admin to speed it up.
            </div>
          </div>
        </div>

        <div className="card">
          <div className="card-head"><h3>Transactions</h3></div>
          <table className="table">
            <thead><tr><th>When</th><th>Type</th><th>Amount</th><th>Status</th></tr></thead>
            <tbody>
              {(txns||[]).slice(0,12).map(t => (
                <tr key={t.id} style={{ cursor: t.status==="pending" ? "pointer" : "default" }} onClick={()=>{ if (t.status==="pending") setActiveTxn(t.id); }}>
                  <td style={{ color: "var(--text-dim)" }}>{fmtTime(t.created_at)}</td>
                  <td><span className="tag">{t.method||"-"}</span> <span style={{ fontSize: 12, color: "var(--text-dim)" }}>{t.note}</span></td>
                  <td className="mono" style={{ fontWeight: 700, color: t.amount>=0?"var(--accent)":"var(--text)" }}>{t.amount>=0?"+":""}{fmtMoney(t.amount)}</td>
                  <td>
                    {t.status==="confirmed" && <span className="pill green"><span className="dot"/>confirmed</span>}
                    {t.status==="pending"   && <span className="pill amber"><span className="dot"/>pending</span>}
                    {t.status==="rejected"  && <span className="pill red"><span className="dot"/>rejected</span>}
                  </td>
                </tr>
              ))}
              {(!txns || !txns.length) && <tr><td colSpan="4" style={{ textAlign: "center", color: "var(--text-dimmer)", padding: 24 }}>No transactions yet.</td></tr>}
            </tbody>
          </table>
        </div>
      </div>
      )}

      {activeTxn && <TopupDetailModal txnId={activeTxn} me={me} onClose={()=>{ setActiveTxn(null); refreshTxns(); refreshPending && refreshPending(); refreshBal(); }}/>}
    </div>
  );
}

function TopupDetailModal({ txnId, me, onClose }) {
  const { data: txns, refresh: refreshTxns } = useApi("/api/balance/txns");
  const { data: pending, refresh: refreshPending } = useApi(me?.role === "admin" ? "/api/balance/pending" : null);
  const { data: messages, refresh: refreshMessages } = useApi(`/api/balance/${txnId}/messages`, [txnId]);
  const t = [...(txns||[]), ...((pending||[]))].find(x => x.id === txnId);
  const [tx, setTx] = React.useState("");
  const [msg, setMsg] = React.useState("");
  React.useEffect(()=>{ setTx(t?.txid||""); }, [t?.txid]);

  if (!t) return <Modal open onClose={onClose} title="Loading…"><div className="card-pad">Loading…</div></Modal>;

  const saveTx = async () => {
    if (!tx.trim()) return;
    try { await api.post(`/api/balance/${txnId}/txid`, { txid: tx.trim() }); toast("TX hash saved"); refreshTxns(); refreshPending && refreshPending(); }
    catch (e) { toast(e.message, "err"); }
  };
  const sendMsg = async () => {
    if (!msg.trim()) return;
    try { await api.post(`/api/balance/${txnId}/messages`, { body: msg.trim() }); setMsg(""); refreshMessages(); }
    catch (e) { toast(e.message, "err"); }
  };
  const approve = async (ok) => {
    try { await api.post(`/api/balance/${txnId}/${ok?"approve":"reject"}`); toast(ok?"Approved":"Rejected"); onClose(); }
    catch (e) { toast(e.message, "err"); }
  };
  const copy = (v) => { navigator.clipboard.writeText(v); toast("Copied"); };

  return (
    <Modal open onClose={onClose} title={`Top-up #${t.id}`} width={620}>
      <div className="flex-col gap-12">
        <div style={{ padding: 14, background: "var(--bg-card-2)", border: "1px solid var(--border)", borderRadius: "var(--r-md)" }}>
          <div className="flex-between" style={{ alignItems: "flex-start" }}>
            <div>
              <div className="eyebrow" style={{ color: "var(--text-dim)" }}>Send exactly</div>
              <div className="mono" style={{ fontSize: 26, fontWeight: 800, color: "var(--accent)" }}>{(+t.amount).toFixed({BTC:8,LTC:8,ETH:6,SOL:6}[t.method]||8)} {t.method}</div>
              <div style={{ fontSize: 11, color: "var(--text-dimmer)", marginTop: 4 }}>credits ${(+(t.amount_requested ?? t.amount)).toFixed(2)} once confirmed</div>
            </div>
            <span className={`pill ${t.status==="pending"?"amber":t.status==="confirmed"?"green":"red"}`}><span className="dot"/>{t.status}</span>
          </div>
          <div className="flex gap-8 mt-12">
            <button className="btn btn-sm" onClick={()=>copy((+t.amount).toFixed({BTC:8,LTC:8,ETH:6,SOL:6}[t.method]||8))}><Ico.Copy className="icon" width={12} height={12}/> Copy amount</button>
            {t.address && <button className="btn btn-sm" onClick={()=>copy(t.address)}><Ico.Copy className="icon" width={12} height={12}/> Copy address</button>}
          </div>
          {t.status === "pending" && (
            <div style={{ fontSize: 11, color: "var(--text-dimmer)", marginTop: 8 }}>
              <Ico.Refresh className="icon" width={11} height={11}/> The system auto-checks the blockchain every minute and approves you the instant the exact amount lands.
            </div>
          )}
        </div>

        {t.status === "pending" && (
          <div className="field">
            <label>TX hash (paste your transaction ID)</label>
            <div className="flex gap-8">
              <input className="input mono" value={tx} onChange={e=>setTx(e.target.value)} placeholder="0x... or bc1..." style={{ flex: 1 }}/>
              <button className="btn btn-primary" onClick={saveTx} disabled={!tx.trim() || tx.trim()===t.txid}>Save</button>
            </div>
          </div>
        )}

        <div className="field">
          <label>Chat with {me?.role==="admin"?"user":"admin"}</label>
          <div style={{ maxHeight: 220, overflow: "auto", padding: 8, background: "var(--bg-card-2)", border: "1px solid var(--border)", borderRadius: "var(--r-md)", display: "flex", flexDirection: "column", gap: 8 }}>
            {(messages||[]).map(m => (
              <div key={m.id} style={{
                alignSelf: m.is_staff ? "flex-start" : "flex-end",
                maxWidth: "80%", padding: "8px 12px",
                background: m.is_staff ? "var(--bg-card)" : "color-mix(in oklab, var(--accent) 14%, var(--bg-card))",
                border: "1px solid " + (m.is_staff ? "var(--border)" : "color-mix(in oklab, var(--accent) 35%, transparent)"),
                borderRadius: "var(--r-md)",
              }}>
                <div style={{ fontSize: 11, color: "var(--text-dimmer)", marginBottom: 4 }}>
                  <b>{m.author}</b> {m.is_staff ? <span className="pill gold" style={{ marginLeft: 4, padding: "1px 6px", fontSize: 9 }}>staff</span> : null} · {fmtTime(m.created_at)}
                </div>
                <div style={{ fontSize: 13, whiteSpace: "pre-wrap" }}>{m.body}</div>
              </div>
            ))}
            {(!messages || !messages.length) && <div style={{ textAlign: "center", color: "var(--text-dimmer)", padding: 14, fontSize: 12 }}>No messages yet.</div>}
          </div>
          <div className="flex gap-8 mt-8">
            <textarea className="textarea" rows="2" value={msg} onChange={e=>setMsg(e.target.value)} placeholder={me?.role==="admin"?"Reply…":"Send a message to admin"} style={{ flex: 1 }}/>
            <button className="btn btn-primary" onClick={sendMsg} disabled={!msg.trim()}><Ico.Arrow className="icon" width={12} height={12}/></button>
          </div>
        </div>

        {me?.role === "admin" && t.status === "pending" && (
          <div className="flex gap-8" style={{ justifyContent: "flex-end" }}>
            <button className="btn" onClick={()=>approve(false)} style={{ color: "var(--danger)" }}>Reject</button>
            <button className="btn btn-primary" onClick={()=>approve(true)}>Approve & credit</button>
          </div>
        )}
      </div>
    </Modal>
  );
}


function LoaderPage({ isAdmin }) {
  const { data: loaders, refresh } = useApi('/api/loaders');
  const { data: products } = useApi('/api/products');
  const [open, setOpen] = React.useState(false);

  const download = (id) => {
    window.location.href = `/api/loaders/${id}/download`;
  };
  const toggleStatus = async (l) => {
    const next = l.status === 'working' ? 'paused' : 'working';
    try { await api.patch(`/api/loaders/${l.id}`, { status: next }); refresh(); }
    catch (e) { toast(e.message,'err'); }
  };
  const remove = async (l) => {
    if (!confirm(`Delete ${l.name}?`)) return;
    try { await api.del(`/api/loaders/${l.id}`); refresh(); toast('Deleted'); }
    catch (e) { toast(e.message,'err'); }
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>Loader</h1>
          <p>{isAdmin ? "Upload new builds, pause detected ones." : "Grab the latest build for your product."}</p>
        </div>
        {isAdmin && <button className="btn btn-primary" onClick={()=>setOpen(true)}><Ico.Up className="icon"/> Upload build</button>}
      </div>

      <div className="card">
        <div className="card-head"><h3>Available builds</h3></div>
        <table className="table">
          <thead><tr><th>Build</th><th>Game</th><th>Anti-cheat</th><th>Status</th><th>Size</th><th>Downloads</th><th>Uploaded</th><th></th></tr></thead>
          <tbody>
            {(loaders||[]).map(l => (
              <tr key={l.id}>
                <td>
                  <div style={{ fontWeight: 700 }}>{l.name}</div>
                  <div className="mono" style={{ fontSize: 11, color: 'var(--text-dimmer)' }}>{l.version}</div>
                </td>
                <td>{l.game || '—'}</td>
                <td>{l.anticheat && l.anticheat.split(',').map(b => <span key={b} className="tag" style={{ marginRight: 4 }}>{b.trim()}</span>)}</td>
                <td>{l.status === 'working' ? <span className="pill green"><span className="dot"/>working</span> : <span className="pill red"><span className="dot"/>paused</span>}</td>
                <td className="mono" style={{ color: 'var(--text-dim)' }}>{((l.size||0)/1024/1024).toFixed(2)} MB</td>
                <td className="mono">{l.downloads.toLocaleString()}</td>
                <td style={{ color: 'var(--text-dim)' }}>{fmtTime(l.uploaded_at)}</td>
                <td>
                  <div className="flex gap-4">
                    <button className="btn btn-sm btn-primary" disabled={l.status!=='working' && !isAdmin} onClick={()=>download(l.id)}><Ico.Down className="icon" width={12} height={12}/></button>
                    {isAdmin && <button className="btn btn-sm" onClick={()=>toggleStatus(l)} title={l.status==='working'?'Pause':'Resume'}>{l.status==='working'?'⏸':'▶'}</button>}
                    {isAdmin && <button className="btn btn-sm" style={{ color: 'var(--danger)' }} onClick={()=>remove(l)}><Ico.Trash className="icon" width={12} height={12}/></button>}
                  </div>
                </td>
              </tr>
            ))}
            {(!loaders || !loaders.length) && <tr><td colSpan="8" style={{ padding: 0 }}>
              <EmptyState
                icon={<Ico.Down/>}
                title={isAdmin?'No loader builds yet':'No downloads available'}
                desc={isAdmin?'Upload a build, tag the anti-cheat, and resellers can grab it from this page.':'Once a build is uploaded, you can download it here.'}
                action={isAdmin && <button className="btn btn-primary" onClick={()=>setOpen(true)}><Ico.Up className="icon"/> Upload first build</button>}
              />
            </td></tr>}
          </tbody>
        </table>
      </div>

      {open && <UploadLoaderModal products={products||[]} onClose={()=>setOpen(false)} onDone={()=>{ setOpen(false); refresh(); }}/>}
    </div>
  );
}

function UploadLoaderModal({ products, onClose, onDone }) {
  const [f, setF] = React.useState({ name: '', version: 'v1.0.0', product_id: products[0]?.id||'', anticheat: 'EAC', notes: '' });
  const [file, setFile] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const submit = async () => {
    if (!file) return toast('Choose a file','warn');
    setBusy(true);
    try {
      await api.upload('/api/loaders', file, { ...f });
      toast('Uploaded'); onDone();
    } catch (e) { toast(e.message,'err'); }
    setBusy(false);
  };
  return (
    <Modal open onClose={onClose} title="Upload loader build" width={560}>
      <div className="flex-col gap-12">
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Name</label><input className="input" value={f.name} onChange={e=>setF({...f,name:e.target.value})} placeholder="Loader name"/></div>
          <div className="field"><label>Version</label><input className="input mono" value={f.version} onChange={e=>setF({...f,version:e.target.value})}/></div>
        </div>
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Product</label>
            <select className="select" value={f.product_id} onChange={e=>setF({...f,product_id:e.target.value})}>
              <option value="">(none)</option>
              {products.map(p=><option key={p.id} value={p.id}>{p.name}</option>)}
            </select>
          </div>
          <div className="field"><label>Anti-cheat</label>
            <select className="select" value={f.anticheat} onChange={e=>setF({...f,anticheat:e.target.value})}>
              <option>EAC</option><option>BattlEye</option><option>Vanguard</option><option>VAC</option><option>Ricochet</option>
            </select>
          </div>
        </div>
        <div className="field"><label>File (max 50MB)</label>
          <input type="file" onChange={e=>setFile(e.target.files[0])}/>
        </div>
        <div className="field"><label>Release notes</label><textarea className="textarea" value={f.notes} onChange={e=>setF({...f,notes:e.target.value})}/></div>
        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={submit}>{busy?'Uploading…':'Upload'}</button>
        </div>
      </div>
    </Modal>
  );
}

Object.assign(window, { ProductsPage, BalancePage, LoaderPage });
