ui: add Billing admin nav, drop Editor nav, replace Editor tile with Premiere panel download modal

- shell.jsx: add Billing item under Admin (routes to the parody pricing
  page); drop Editor from the Operations section
- app.jsx: route 'billing' to TokensParody; remove 'editor' and
  'tokens-parody' routes
- screens-admin.jsx: rename parody h1 from 'Tokens' to 'Billing'; drop
  the cross-link from the real Tokens page (no longer needed)
- screens-home.jsx: replace the Editor launcher tile with a 'Premiere
  panel' tile that opens a new PremiereDownloadModal listing every
  registered release (ZXP + Windows installer) with version + LATEST
  badge + release date + notes
- styles-screens.css: .premiere-release-* row styles for the modal

Editor screen + nav button retired; users get the Premiere panel as the
recommended editor instead, with all download options in one place from
Home.
This commit is contained in:
Zac Gaetano 2026-05-29 00:01:19 +00:00
parent 342b56af35
commit 303f12e0f9
5 changed files with 113 additions and 20 deletions

View file

@ -113,10 +113,9 @@ function App() {
case 'capture': content = <Capture navigate={navigate} />; break;
case 'monitors': content = <Monitors navigate={navigate} />; break;
case 'jobs': content = <Jobs navigate={navigate} />; break;
case 'editor': content = <Editor />; break;
case 'users': content = <Users />; break;
case 'tokens': content = <Tokens />; break;
case 'tokens-parody': content = <TokensParody />; break;
case 'billing': content = <TokensParody />; break;
case 'containers':content = <Containers />; break;
case 'cluster': content = <Cluster />; break;
case 'settings': content = <Settings />; break;

View file

@ -538,8 +538,8 @@ function GroupsPanel({ groups, users, onChange }) {
// Real Tokens admin page: wraps ApiTokensSection (defined further down) in a
// .page shell so it can be a top-level admin nav destination. The old parody
// page lives below as TokensParody and is still reachable via the hidden
// `tokens-parody` route for posterity.
// pricing page lives below as TokensParody and is now routed at /billing in
// the Admin section.
function Tokens() {
return (
<div className="page">
@ -549,9 +549,6 @@ function Tokens() {
</div>
<div className="page-body">
<ApiTokensSection />
<div style={{ marginTop: 16, fontSize: 11.5, color: 'var(--text-3)' }}>
Looking for the old satirical pricing page? <a href="#tokens-parody" onClick={(e) => { e.preventDefault(); window.dispatchEvent(new CustomEvent('df:nav', { detail: 'tokens-parody' })); }} style={{ color: 'var(--accent-text)' }}>It's still here.</a>
</div>
</div>
</div>
);
@ -611,7 +608,7 @@ function TokensParody() {
return (
<div className="page">
<div className="page-header">
<h1>Tokens</h1>
<h1>Billing</h1>
<span className="subtitle">Token-metered pricing parody · You actually pay <strong style={{ color: "var(--success)" }}>$0.00</strong></span>
<div className="spacer" />
<span className="badge warning"><Icon name="alert" size={10} /> SATIRE</span>

View file

@ -18,6 +18,8 @@
// Anything that would just say "all clear" is hidden, not rendered.
function Home({ navigate }) {
const [showPremiereDownload, setShowPremiereDownload] = React.useState(false);
// Pull live counts so the tile subtitles ("34 assets", "0 live", "3 running")
// reflect what's actually in the DB right now, not a stale boot-time cache.
const [cards, setCards] = React.useState({});
@ -62,12 +64,12 @@ function Home({ navigate }) {
desc: 'SDI · SRT · RTMP ingest. Start, stop, schedule.',
},
{
id: 'editor',
label: 'Editor',
id: '__premiere',
label: 'Premiere panel',
icon: 'editor',
tone: 'purple',
sub: 'Beta',
desc: 'Timeline editor with cross-clip preview and render queue.',
sub: 'v' + ((window.PREMIERE_LATEST || {}).version || '·'),
desc: 'Download the Adobe Premiere Pro panel for frame-accurate editing.',
},
{
id: 'jobs',
@ -125,7 +127,7 @@ function Home({ navigate }) {
<button
key={t.id}
className={'launcher-tile tone-' + t.tone}
onClick={() => navigate(t.id)}
onClick={() => t.id === '__premiere' ? setShowPremiereDownload(true) : navigate(t.id)}
>
<span className="launcher-tile-icon">
<Icon name={t.icon} size={26} />
@ -239,6 +241,78 @@ function Home({ navigate }) {
)}
</div>
</div>
{showPremiereDownload && <PremiereDownloadModal onClose={() => setShowPremiereDownload(false)} />}
</div>
);
}
// Modal listing all Premiere panel downloads (ZXP + Windows installer for
// each released version). Sourced from window.PREMIERE_RELEASES, written by
// the Settings SDKs section in screens-admin.jsx.
function PremiereDownloadModal({ onClose }) {
const releases = (window.PREMIERE_RELEASES || []).slice().sort((a, b) => {
// Newest first; fall back to lexicographic compare on version string.
const av = String(a.version || ''), bv = String(b.version || '');
return bv.localeCompare(av, undefined, { numeric: true });
});
const latest = window.PREMIERE_LATEST || releases[0] || null;
return (
<div className="modal-backdrop" onClick={onClose}>
<div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 560 }}>
<div className="modal-head">
<div>
<div style={{ fontSize: 15, fontWeight: 600 }}>Premiere panel</div>
<div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 2 }}>
Adobe Premiere Pro integration. Install one ZXP per workstation.
</div>
</div>
<button className="icon-btn" aria-label="Close" onClick={onClose}><Icon name="x" /></button>
</div>
<div className="modal-body">
{releases.length === 0 && (
<div style={{ padding: '24px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
No releases registered yet. Upload one from Settings Capture SDKs.
</div>
)}
{releases.map((rel, i) => (
<div key={rel.version || i} className="premiere-release">
<div className="premiere-release-head">
<span className="premiere-release-version mono">v{rel.version}</span>
{latest && latest.version === rel.version && (
<span className="badge accent">LATEST</span>
)}
{rel.released_at && (
<span className="premiere-release-date mono">
{new Date(rel.released_at).toLocaleDateString()}
</span>
)}
</div>
{rel.notes && <div className="premiere-release-notes">{rel.notes}</div>}
<div className="premiere-release-actions">
{rel.zxp && (
<a href={rel.zxp} download className="btn primary sm">
<Icon name="download" />ZXP
</a>
)}
{rel.installer && (
<a href={rel.installer} download className="btn ghost sm">
<Icon name="download" />Windows installer
</a>
)}
</div>
</div>
))}
</div>
<div className="modal-foot">
<div style={{ flex: 1, fontSize: 11.5, color: 'var(--text-3)' }}>
Need help installing? Use the Adobe Extension Manager or UPIA.
</div>
<button className="btn ghost" onClick={onClose}>Close</button>
</div>
</div>
</div>
);
}

View file

@ -29,7 +29,6 @@ const NAV_SECTIONS = [
items: [
{ id: "capture", label: "Capture", icon: "capture" },
{ id: "jobs", label: "Jobs", icon: "jobs" },
{ id: "editor", label: "Editor", icon: "editor", badge: { kind: 'neutral', text: 'BETA' } },
],
},
{
@ -37,6 +36,7 @@ const NAV_SECTIONS = [
items: [
{ id: "users", label: "Users", icon: "users" },
{ id: "tokens", label: "Tokens", icon: "token" },
{ id: "billing", label: "Billing", icon: "token" },
{ id: "containers", label: "Containers", icon: "container" },
{ id: "cluster", label: "Cluster", icon: "cluster" },
{ id: "settings", label: "Settings", icon: "settings" },
@ -44,12 +44,9 @@ const NAV_SECTIONS = [
},
];
// Hidden routes: not in the sidebar but still reachable by direct nav.
// `tokens-parody` is the old satirical pricing page (see issue #152). Real
// API token management lives at /tokens (in the Admin section above).
const NAV_HIDDEN = [
{ id: "tokens-parody", label: "Tokens (parody)", icon: "token" },
];
// No hidden routes currently; Billing (the satirical pricing page) lives in
// the Admin section above. Real API token management is at /tokens.
const NAV_HIDDEN = [];
// Back-compat: NAV_TREE and ADMIN_TREE were used by other modules.
// NAV_FLAT is consumed by topbar search and the keyboard router.

View file

@ -1346,3 +1346,29 @@
justify-content: flex-end;
}
.job-row .job-row-cancel { color: var(--danger); }
/* Premiere panel download modal (rows for each released version) */
.premiere-release {
display: flex; flex-direction: column;
gap: 6px;
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.premiere-release:last-child { border-bottom: 0; }
.premiere-release-head {
display: flex; align-items: center; gap: 8px;
}
.premiere-release-version {
font-size: 13px; font-weight: 600; color: var(--text-1);
}
.premiere-release-date {
font-size: 11px; color: var(--text-3);
margin-left: auto;
}
.premiere-release-notes {
font-size: 12px; color: var(--text-2); line-height: 1.5;
}
.premiere-release-actions {
display: flex; gap: 8px; margin-top: 4px;
}
.premiere-release-actions a { text-decoration: none; }