SaaS — Customers / Accounts Admin
An internal customers and accounts admin built as a dense, clean data-table. Browse fictional company accounts with plan badges, MRR, seat counts, status, and health indicators, then refine with live search and plan and status filters and sortable columns. Click any row to open a detail drawer with overview, plan, usage meters, activity feed, and billing history. Includes row selection with bulk export, CSV download, pagination, and a working light and dark theme toggle.
MCP
Code
:root {
--bg: #f7f8fb;
--surface: #ffffff;
--surface-2: #fafbfd;
--ink: #0f1222;
--muted: #646b85;
--faint: #8b91a8;
--brand: #6366f1;
--brand-d: #4f46e5;
--brand-soft: rgba(99, 102, 241, .1);
--ok: #16a34a;
--ok-soft: rgba(22, 163, 74, .12);
--warn: #d97706;
--warn-soft: rgba(217, 119, 6, .14);
--danger: #dc2626;
--danger-soft: rgba(220, 38, 38, .12);
--line: rgba(15, 18, 34, .1);
--line-2: rgba(15, 18, 34, .06);
--shadow: 0 1px 2px rgba(15, 18, 34, .04), 0 8px 24px rgba(15, 18, 34, .06);
--shadow-lg: 0 24px 64px rgba(15, 18, 34, .22);
--radius: 12px;
--sidebar-w: 224px;
}
[data-theme="dark"] {
--bg: #0b0d16;
--surface: #141826;
--surface-2: #1a1f30;
--ink: #eef0f7;
--muted: #9aa1bd;
--faint: #6e7596;
--brand-soft: rgba(99, 102, 241, .2);
--ok-soft: rgba(22, 163, 74, .2);
--warn-soft: rgba(217, 119, 6, .22);
--danger-soft: rgba(220, 38, 38, .2);
--line: rgba(255, 255, 255, .1);
--line-2: rgba(255, 255, 255, .06);
--shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .4);
--shadow-lg: 0 24px 64px rgba(0, 0, 0, .6);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 14px;
}
.vh, .skip { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip {
left: 8px; top: 8px; width: auto; height: auto; clip: auto;
background: var(--brand); color: #fff; padding: 8px 12px; border-radius: 8px; z-index: 50;
transform: translateY(-150%); transition: transform .15s;
}
.skip:focus { transform: translateY(0); }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 6px; }
button, select, input { font: inherit; color: inherit; }
/* Layout */
.app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
/* Sidebar */
.sidebar {
background: var(--surface);
border-right: 1px solid var(--line);
display: flex; flex-direction: column; gap: 4px;
padding: 18px 14px;
position: sticky; top: 0; height: 100vh;
}
.brand { display: flex; align-items: center; gap: 8px; padding: 4px 6px 16px; }
.brand-mark {
display: grid; place-items: center; width: 30px; height: 30px; border-radius: 9px;
background: linear-gradient(135deg, var(--brand), var(--brand-d)); color: #fff;
}
.brand-name { font-weight: 700; letter-spacing: -.01em; }
.brand-tag {
font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
color: var(--brand-d); background: var(--brand-soft); padding: 2px 6px; border-radius: 999px;
}
.nav { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.nav-item {
display: flex; align-items: center; gap: 10px;
padding: 9px 10px; border-radius: 9px; color: var(--muted);
text-decoration: none; font-weight: 500; transition: background .12s, color .12s;
}
.nav-item .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .45; }
.nav-item:hover { background: var(--line-2); color: var(--ink); }
.nav-item.is-active { background: var(--brand-soft); color: var(--brand-d); font-weight: 600; }
[data-theme="dark"] .nav-item.is-active { color: #c7c9ff; }
.nav-item.is-active .dot { opacity: 1; }
.sidebar-foot { margin-top: auto; border-top: 1px solid var(--line-2); padding-top: 12px; }
.who { display: flex; align-items: center; gap: 10px; padding: 4px 6px; }
.avatar {
display: grid; place-items: center; width: 32px; height: 32px; border-radius: 50%;
background: var(--brand-soft); color: var(--brand-d); font-weight: 700; font-size: 12px;
}
[data-theme="dark"] .avatar { color: #c7c9ff; }
.who-meta { display: flex; flex-direction: column; line-height: 1.25; }
.who-meta small { color: var(--faint); font-size: 11px; }
/* Main */
.main { display: flex; flex-direction: column; min-width: 0; }
.topbar {
position: sticky; top: 0; z-index: 20;
display: flex; align-items: center; justify-content: space-between;
padding: 12px 22px; background: color-mix(in srgb, var(--surface) 88%, transparent);
backdrop-filter: blur(8px); border-bottom: 1px solid var(--line);
}
.crumbs { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 13px; }
.crumbs strong { color: var(--ink); }
.topbar-actions { display: flex; align-items: center; gap: 8px; }
/* Buttons */
.btn {
display: inline-flex; align-items: center; gap: 6px;
border: 1px solid var(--line); background: var(--surface); color: var(--ink);
padding: 8px 12px; border-radius: 9px; font-weight: 600; font-size: 13px;
cursor: pointer; transition: background .12s, border-color .12s, transform .06s, box-shadow .12s;
white-space: nowrap;
}
.btn:hover { background: var(--surface-2); border-color: color-mix(in srgb, var(--ink) 22%, var(--line)); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: 0 1px 2px rgba(79, 70, 229, .35); }
.btn-primary:hover { background: var(--brand-d); border-color: var(--brand-d); }
.btn-ghost { background: transparent; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-mini { padding: 5px 10px; font-size: 12px; border-radius: 7px; }
.btn-link { border: none; background: none; color: var(--muted); padding: 5px 8px; }
.btn-link:hover { color: var(--ink); background: var(--line-2); }
.btn-warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 35%, var(--line)); }
.btn-warn:hover { background: var(--warn-soft); }
/* Page */
.page { padding: 22px; max-width: 1180px; width: 100%; margin: 0 auto; }
.page-head h1 { margin: 0; font-size: 22px; letter-spacing: -.02em; }
.page-head .sub { margin: 2px 0 0; color: var(--muted); }
/* Stats */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 18px 0; }
.stat {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 14px; display: flex; flex-direction: column; gap: 4px; box-shadow: var(--shadow);
}
.stat-label { color: var(--muted); font-size: 12px; font-weight: 500; }
.stat-val { font-size: 22px; letter-spacing: -.02em; }
.stat-trend { font-size: 11px; font-weight: 600; }
.stat-trend.up { color: var(--ok); }
.stat-trend.down { color: var(--warn); }
/* Toolbar */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.search { position: relative; flex: 1 1 240px; min-width: 200px; }
.search-ico { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--faint); font-size: 16px; }
.search input {
width: 100%; padding: 9px 12px 9px 34px; border-radius: 9px;
border: 1px solid var(--line); background: var(--surface); color: var(--ink);
}
.search input::placeholder { color: var(--faint); }
.select select {
appearance: none; padding: 9px 30px 9px 12px; border-radius: 9px;
border: 1px solid var(--line); background: var(--surface);
background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%;
background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
cursor: pointer; font-weight: 500;
}
/* Bulk bar */
.bulkbar {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
background: var(--brand-soft); border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--line));
border-radius: 10px; padding: 8px 12px; margin-bottom: 12px; font-size: 13px;
}
.bulkbar strong { color: var(--brand-d); }
[data-theme="dark"] .bulkbar strong { color: #c7c9ff; }
/* Table */
.table-wrap {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
box-shadow: var(--shadow); overflow: hidden;
}
.grid { width: 100%; border-collapse: collapse; }
.grid thead th {
text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
color: var(--muted); font-weight: 600; padding: 0;
background: var(--surface-2); border-bottom: 1px solid var(--line);
position: sticky; top: 0;
}
.grid th.num { text-align: right; }
.grid th.col-check { padding: 0 0 0 14px; width: 38px; }
.grid th.col-act { width: 44px; }
.sort {
display: inline-flex; align-items: center; gap: 5px; width: 100%;
background: none; border: none; cursor: pointer; padding: 11px 14px;
font: inherit; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
color: var(--muted); font-weight: 600;
}
.grid th.num .sort { justify-content: flex-end; }
.sort:hover { color: var(--ink); }
.caret { width: 8px; height: 8px; opacity: 0; transition: opacity .12s; }
.sort.is-sorted { color: var(--ink); }
.sort.is-sorted .caret { opacity: 1; }
.sort.is-sorted .caret::before { content: "▲"; font-size: 8px; }
.sort.is-sorted.desc .caret::before { content: "▼"; }
.grid tbody td { padding: 11px 14px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.grid tbody tr:last-child td { border-bottom: none; }
.grid tbody tr { transition: background .1s; cursor: pointer; }
.grid tbody tr:hover { background: var(--surface-2); }
.grid tbody tr.is-selected { background: var(--brand-soft); }
.grid td.num { text-align: right; font-variant-numeric: tabular-nums; }
.grid td.col-check { padding-left: 14px; }
input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; }
.co { display: flex; align-items: center; gap: 11px; }
.co-logo {
display: grid; place-items: center; width: 32px; height: 32px; border-radius: 8px;
font-weight: 700; font-size: 12px; color: #fff; flex-shrink: 0;
}
.co-meta { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.co-meta strong { letter-spacing: -.01em; }
.co-meta small { color: var(--faint); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 3px 9px; border-radius: 999px; font-size: 12px; font-weight: 600;
border: 1px solid transparent; white-space: nowrap;
}
.plan-Free { background: var(--line-2); color: var(--muted); }
.plan-Starter { background: rgba(14, 165, 233, .12); color: #0284c7; }
.plan-Growth { background: var(--brand-soft); color: var(--brand-d); }
.plan-Scale { background: rgba(168, 85, 247, .14); color: #9333ea; }
.plan-Enterprise { background: rgba(15, 18, 34, .85); color: #fff; }
[data-theme="dark"] .plan-Enterprise { background: #fff; color: #0f1222; }
[data-theme="dark"] .plan-Starter { color: #7dd3fc; }
[data-theme="dark"] .plan-Growth { color: #c7c9ff; }
[data-theme="dark"] .plan-Scale { color: #d8b4fe; }
.status { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: 12px; }
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.status.active { color: var(--ok); }
.status.trialing { color: var(--brand-d); }
.status.past_due { color: var(--warn); }
.status.churned { color: var(--faint); }
[data-theme="dark"] .status.trialing { color: #a5a8ff; }
.health { display: inline-flex; align-items: center; gap: 7px; }
.health-bar { width: 54px; height: 6px; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.health-bar i { display: block; height: 100%; border-radius: 999px; }
.health-num { font-variant-numeric: tabular-nums; font-weight: 600; font-size: 12px; width: 22px; }
.h-good i { background: var(--ok); }
.h-mid i { background: var(--warn); }
.h-bad i { background: var(--danger); }
.h-good .health-num { color: var(--ok); }
.h-mid .health-num { color: var(--warn); }
.h-bad .health-num { color: var(--danger); }
.row-act {
border: none; background: none; cursor: pointer; color: var(--faint);
width: 28px; height: 28px; border-radius: 7px; font-size: 16px; line-height: 1;
}
.row-act:hover { background: var(--line-2); color: var(--ink); }
/* Empty */
.empty { padding: 56px 20px; text-align: center; color: var(--muted); display: flex; flex-direction: column; align-items: center; gap: 12px; }
.empty-ico { font-size: 30px; opacity: .4; }
.empty p { margin: 0; }
/* Pager */
.pager { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.pager-info { color: var(--muted); font-size: 13px; }
.pager-ctl { display: flex; align-items: center; gap: 6px; }
.pager-pages { display: flex; gap: 4px; }
.page-num {
min-width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--line);
background: var(--surface); color: var(--ink); cursor: pointer; font-weight: 600; font-size: 13px;
}
.page-num:hover { background: var(--surface-2); }
.page-num.is-current { background: var(--brand); border-color: var(--brand); color: #fff; }
/* Drawer */
.scrim {
position: fixed; inset: 0; background: rgba(15, 18, 34, .42);
backdrop-filter: blur(2px); z-index: 60; opacity: 0; transition: opacity .2s;
}
.scrim.show { opacity: 1; }
.drawer {
position: fixed; top: 0; right: 0; height: 100vh; width: min(440px, 94vw);
background: var(--surface); border-left: 1px solid var(--line); box-shadow: var(--shadow-lg);
z-index: 61; transform: translateX(100%); transition: transform .26s cubic-bezier(.4, 0, .2, 1);
overflow-y: auto; overscroll-behavior: contain;
}
.drawer.open { transform: translateX(0); }
.drawer-inner { padding: 20px; }
.dh { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 18px; }
.dh .co-logo { width: 44px; height: 44px; border-radius: 11px; font-size: 15px; }
.dh-meta { flex: 1; min-width: 0; }
.dh-meta h2 { margin: 0; font-size: 18px; letter-spacing: -.01em; }
.dh-meta .dh-sub { color: var(--muted); font-size: 13px; margin-top: 2px; }
.dclose {
border: 1px solid var(--line); background: var(--surface); color: var(--muted);
width: 32px; height: 32px; border-radius: 8px; cursor: pointer; font-size: 18px; line-height: 1; flex-shrink: 0;
}
.dclose:hover { background: var(--surface-2); color: var(--ink); }
.dgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 18px; }
.dcell { background: var(--surface-2); border: 1px solid var(--line-2); border-radius: 10px; padding: 11px 13px; }
.dcell span { display: block; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.dcell strong { font-size: 16px; letter-spacing: -.01em; }
.dsection { margin-bottom: 18px; }
.dsection h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 9px; }
.usage-row { display: flex; flex-direction: column; gap: 5px; margin-bottom: 11px; }
.usage-row .ur-top { display: flex; justify-content: space-between; font-size: 13px; }
.usage-row .ur-top em { color: var(--muted); font-style: normal; }
.ubar { height: 7px; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.ubar i { display: block; height: 100%; background: var(--brand); border-radius: 999px; }
.ubar.warn i { background: var(--warn); }
.activity { list-style: none; margin: 0; padding: 0; }
.activity li { display: flex; gap: 11px; padding: 8px 0; border-bottom: 1px solid var(--line-2); }
.activity li:last-child { border: none; }
.act-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; background: var(--brand); flex-shrink: 0; }
.act-dot.ok { background: var(--ok); }
.act-dot.warn { background: var(--warn); }
.act-body { flex: 1; }
.act-body p { margin: 0; font-size: 13px; }
.act-body time { color: var(--faint); font-size: 11px; }
.bill-row { display: flex; justify-content: space-between; align-items: center; padding: 9px 0; border-bottom: 1px solid var(--line-2); font-size: 13px; }
.bill-row:last-child { border: none; }
.bill-row .bill-amt { font-variant-numeric: tabular-nums; font-weight: 600; }
.pill { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }
.pill.paid { background: var(--ok-soft); color: var(--ok); }
.pill.due { background: var(--warn-soft); color: var(--warn); }
.dactions { display: flex; gap: 8px; margin-top: 4px; }
.dactions .btn { flex: 1; justify-content: center; }
/* Toast */
.toast-wrap { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.toast {
background: var(--ink); color: var(--bg); padding: 10px 16px; border-radius: 10px;
font-size: 13px; font-weight: 500; box-shadow: var(--shadow-lg);
animation: toast-in .22s ease both;
}
[data-theme="dark"] .toast { background: var(--surface); color: var(--ink); border: 1px solid var(--line); }
.toast.out { animation: toast-out .2s ease both; }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(10px); } }
/* Responsive */
@media (max-width: 980px) {
.app { grid-template-columns: 1fr; }
.sidebar {
position: static; height: auto; flex-direction: row; flex-wrap: wrap; align-items: center;
gap: 8px; padding: 10px 14px;
}
.nav { flex-direction: row; overflow-x: auto; gap: 4px; flex: 1; }
.nav-item { white-space: nowrap; }
.sidebar-foot { display: none; }
.stats { grid-template-columns: repeat(2, 1fr); }
.grid th.col-act, .grid td.col-act { display: none; }
}
@media (max-width: 680px) {
.grid thead th:nth-child(4), .grid tbody td:nth-child(4),
.grid thead th:nth-child(5), .grid tbody td:nth-child(5) { display: none; }
.page { padding: 16px; }
.dgrid { grid-template-columns: 1fr; }
}
@media (max-width: 420px) {
.stats { grid-template-columns: 1fr 1fr; }
.grid thead th:nth-child(7), .grid tbody td:nth-child(7) { display: none; }
.co-meta small { display: none; }
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; animation: none !important; }
}(function () {
"use strict";
/* ---------- Fictional dataset ---------- */
var LOGOS = ["#6366f1", "#0ea5e9", "#9333ea", "#16a34a", "#d97706", "#dc2626", "#0891b2", "#db2777", "#65a30d", "#7c3aed"];
function logoColor(name) {
var s = 0;
for (var i = 0; i < name.length; i++) s = (s + name.charCodeAt(i)) % LOGOS.length;
return LOGOS[s];
}
function initials(name) {
return name.replace(/[^A-Za-z0-9 ]/g, "").split(/\s+/).slice(0, 2).map(function (w) { return w[0]; }).join("").toUpperCase();
}
var DATA = [
{ id: "AC-1042", company: "Northwind Labs", owner: "Priya Anand", email: "[email protected]", plan: "Scale", mrr: 4200, seats: 48, status: "active", health: 92, since: "Mar 2024", region: "US-East" },
{ id: "AC-1088", company: "Vellum Studio", owner: "Marco Diaz", email: "[email protected]", plan: "Growth", mrr: 890, seats: 14, status: "active", health: 78, since: "Jul 2024", region: "EU-West" },
{ id: "AC-1131", company: "Tilde Analytics", owner: "Sara Okafor", email: "[email protected]", plan: "Enterprise", mrr: 9800, seats: 120, status: "active", health: 88, since: "Nov 2023", region: "US-West" },
{ id: "AC-1156", company: "Pinecrest Co", owner: "James Whitfield", email: "[email protected]", plan: "Starter", mrr: 149, seats: 5, status: "trialing", health: 64, since: "Jun 2026", region: "US-East" },
{ id: "AC-1190", company: "Quanta Freight", owner: "Lena Brandt", email: "[email protected]", plan: "Growth", mrr: 1240, seats: 22, status: "past_due", health: 41, since: "Feb 2025", region: "EU-Central" },
{ id: "AC-1203", company: "Maple & Bell", owner: "Omar Haddad", email: "[email protected]", plan: "Free", mrr: 0, seats: 3, status: "trialing", health: 55, since: "Jun 2026", region: "APAC" },
{ id: "AC-1244", company: "Brightside Health", owner: "Nina Petrov", email: "[email protected]", plan: "Scale", mrr: 3650, seats: 41, status: "active", health: 84, since: "Sep 2024", region: "US-East" },
{ id: "AC-1267", company: "Orbital Goods", owner: "Tom Becker", email: "[email protected]", plan: "Starter", mrr: 99, seats: 4, status: "active", health: 71, since: "Jan 2025", region: "EU-West" },
{ id: "AC-1289", company: "Cobalt Robotics", owner: "Ava Lindqvist", email: "[email protected]", plan: "Enterprise", mrr: 12400, seats: 165, status: "active", health: 95, since: "May 2023", region: "US-West" },
{ id: "AC-1310", company: "Fernwood Bank", owner: "Derek Cole", email: "[email protected]", plan: "Scale", mrr: 5100, seats: 60, status: "past_due", health: 38, since: "Aug 2024", region: "US-Central" },
{ id: "AC-1334", company: "Lumen Print", owner: "Hana Suzuki", email: "[email protected]", plan: "Growth", mrr: 760, seats: 11, status: "active", health: 73, since: "Apr 2025", region: "APAC" },
{ id: "AC-1356", company: "Drift Surf Club", owner: "Kai Moana", email: "[email protected]", plan: "Free", mrr: 0, seats: 2, status: "churned", health: 18, since: "Dec 2024", region: "APAC" },
{ id: "AC-1377", company: "Atlas Survey", owner: "Greta Nilsen", email: "[email protected]", plan: "Growth", mrr: 1080, seats: 19, status: "active", health: 81, since: "Oct 2024", region: "EU-North" },
{ id: "AC-1402", company: "Maven Legal", owner: "Robert Asante", email: "[email protected]", plan: "Scale", mrr: 3990, seats: 44, status: "active", health: 67, since: "Mar 2025", region: "US-East" },
{ id: "AC-1419", company: "Saffron Eats", owner: "Devi Rao", email: "[email protected]", plan: "Starter", mrr: 199, seats: 7, status: "trialing", health: 60, since: "Jun 2026", region: "APAC" },
{ id: "AC-1433", company: "Polaris Energy", owner: "Mikael Berg", email: "[email protected]", plan: "Enterprise", mrr: 15200, seats: 210, status: "active", health: 90, since: "Feb 2023", region: "EU-North" },
{ id: "AC-1458", company: "Glimmer Cosmetics", owner: "Chloe Tran", email: "[email protected]", plan: "Growth", mrr: 940, seats: 16, status: "active", health: 76, since: "Jul 2025", region: "US-West" },
{ id: "AC-1471", company: "Hollowbrook Farms", owner: "Pete Carlson", email: "[email protected]", plan: "Free", mrr: 0, seats: 2, status: "churned", health: 24, since: "Nov 2024", region: "US-Central" },
{ id: "AC-1490", company: "Kestrel Aero", owner: "Yara Mansour", email: "[email protected]", plan: "Scale", mrr: 4480, seats: 52, status: "active", health: 86, since: "Jan 2024", region: "EU-West" },
{ id: "AC-1512", company: "Tessera Tiles", owner: "Bruno Costa", email: "[email protected]", plan: "Starter", mrr: 129, seats: 5, status: "past_due", health: 47, since: "May 2025", region: "LATAM" },
{ id: "AC-1538", company: "Wavelength FM", owner: "Indie Park", email: "[email protected]", plan: "Growth", mrr: 1320, seats: 24, status: "active", health: 83, since: "Sep 2024", region: "APAC" },
{ id: "AC-1559", company: "Granite Tax", owner: "Holly Reed", email: "[email protected]", plan: "Enterprise", mrr: 8700, seats: 98, status: "active", health: 79, since: "Apr 2024", region: "US-East" },
{ id: "AC-1574", company: "Mosaic Schools", owner: "Felix Wagner", email: "[email protected]", plan: "Scale", mrr: 3300, seats: 38, status: "trialing", health: 69, since: "Jun 2026", region: "EU-Central" },
{ id: "AC-1601", company: "Driftwood Hotels", owner: "Camila Rojas", email: "[email protected]", plan: "Growth", mrr: 1010, seats: 18, status: "active", health: 74, since: "Aug 2025", region: "LATAM" },
{ id: "AC-1623", company: "Verdant Supply", owner: "Leo Chambers", email: "[email protected]", plan: "Starter", mrr: 159, seats: 6, status: "active", health: 72, since: "Feb 2025", region: "US-West" },
{ id: "AC-1645", company: "Halcyon Media", owner: "Ruth Adeyemi", email: "[email protected]", plan: "Scale", mrr: 4720, seats: 55, status: "active", health: 88, since: "Dec 2023", region: "EU-West" }
];
var PLAN_RANK = { Free: 0, Starter: 1, Growth: 2, Scale: 3, Enterprise: 4 };
var STATUS_LABEL = { active: "Active", trialing: "Trialing", past_due: "Past due", churned: "Churned" };
/* ---------- State ---------- */
var state = {
search: "", plan: "", status: "",
sortKey: "mrr", sortDir: "desc",
page: 1, pageSize: 8,
selected: new Set()
};
var $ = function (s, r) { return (r || document).querySelector(s); };
var fmtMoney = function (n) { return "$" + n.toLocaleString("en-US"); };
var fmtMoneyK = function (n) { return n >= 1000 ? "$" + (n / 1000).toFixed(n % 1000 === 0 ? 0 : 1) + "k" : "$" + n; };
/* ---------- Toast ---------- */
function toast(msg) {
var wrap = $("#toasts");
var el = document.createElement("div");
el.className = "toast";
el.textContent = msg;
wrap.appendChild(el);
setTimeout(function () {
el.classList.add("out");
setTimeout(function () { el.remove(); }, 220);
}, 2400);
}
/* ---------- Derived list ---------- */
function filtered() {
var q = state.search.trim().toLowerCase();
var list = DATA.filter(function (c) {
if (state.plan && c.plan !== state.plan) return false;
if (state.status && c.status !== state.status) return false;
if (q) {
var hay = (c.company + " " + c.owner + " " + c.email + " " + c.id).toLowerCase();
if (hay.indexOf(q) === -1) return false;
}
return true;
});
var dir = state.sortDir === "asc" ? 1 : -1;
var k = state.sortKey;
list.sort(function (a, b) {
var av, bv;
if (k === "plan") { av = PLAN_RANK[a.plan]; bv = PLAN_RANK[b.plan]; }
else if (k === "company" || k === "status") { av = a[k].toLowerCase(); bv = b[k].toLowerCase(); }
else { av = a[k]; bv = b[k]; }
if (av < bv) return -1 * dir;
if (av > bv) return 1 * dir;
return a.company.localeCompare(b.company);
});
return list;
}
function healthClass(h) { return h >= 75 ? "h-good" : h >= 50 ? "h-mid" : "h-bad"; }
/* ---------- Render rows ---------- */
function render() {
var list = filtered();
var total = list.length;
var pages = Math.max(1, Math.ceil(total / state.pageSize));
if (state.page > pages) state.page = pages;
var start = (state.page - 1) * state.pageSize;
var pageItems = list.slice(start, start + state.pageSize);
var tbody = $("#rows");
tbody.innerHTML = "";
pageItems.forEach(function (c) {
var tr = document.createElement("tr");
tr.dataset.id = c.id;
if (state.selected.has(c.id)) tr.classList.add("is-selected");
var hc = healthClass(c.health);
tr.innerHTML =
'<td class="col-check"><input type="checkbox" class="rowcheck" aria-label="Select ' + esc(c.company) + '"' + (state.selected.has(c.id) ? " checked" : "") + "></td>" +
'<td><div class="co"><span class="co-logo" style="background:' + logoColor(c.company) + '">' + initials(c.company) + '</span>' +
'<span class="co-meta"><strong>' + esc(c.company) + "</strong><small>" + esc(c.owner) + " · " + c.id + "</small></span></div></td>" +
'<td><span class="badge plan-' + c.plan + '">' + c.plan + "</span></td>" +
'<td class="num">' + fmtMoney(c.mrr) + "</td>" +
'<td class="num">' + c.seats + "</td>" +
'<td><span class="status ' + c.status + '">' + STATUS_LABEL[c.status] + "</span></td>" +
'<td><span class="health ' + hc + '"><span class="health-bar"><i style="width:' + c.health + '%"></i></span><span class="health-num">' + c.health + "</span></span></td>" +
'<td class="col-act"><button class="row-act" aria-label="Open ' + esc(c.company) + '">›</button></td>';
tbody.appendChild(tr);
});
$("#empty").hidden = total !== 0;
$(".table-wrap .grid").style.display = total === 0 ? "none" : "";
// pager
var shownEnd = Math.min(start + state.pageSize, total);
$("#pagerInfo").textContent = total === 0 ? "No results" :
"Showing " + (start + 1) + "–" + shownEnd + " of " + total + " accounts";
renderPages(pages);
$("#prevPage").disabled = state.page <= 1;
$("#nextPage").disabled = state.page >= pages;
// check-all reflects current page
var allChecked = pageItems.length > 0 && pageItems.every(function (c) { return state.selected.has(c.id); });
$("#checkAll").checked = allChecked;
$("#checkAll").indeterminate = !allChecked && pageItems.some(function (c) { return state.selected.has(c.id); });
renderBulk();
}
function renderPages(pages) {
var wrap = $("#pagerPages");
wrap.innerHTML = "";
for (var p = 1; p <= pages; p++) {
var b = document.createElement("button");
b.className = "page-num" + (p === state.page ? " is-current" : "");
b.textContent = p;
b.setAttribute("aria-label", "Page " + p);
if (p === state.page) b.setAttribute("aria-current", "page");
(function (pp) { b.addEventListener("click", function () { state.page = pp; render(); }); })(p);
wrap.appendChild(b);
}
}
function renderBulk() {
var n = state.selected.size;
var bar = $("#bulkbar");
bar.hidden = n === 0;
$("#bulkCount").textContent = n;
}
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); }
/* ---------- Stats ---------- */
function renderStats() {
var mrr = 0, seats = 0, risk = 0;
DATA.forEach(function (c) {
mrr += c.mrr; seats += c.seats;
if (c.status === "past_due" || c.status === "churned" || c.health < 50) risk++;
});
$("#statAccounts").textContent = DATA.length;
$("#statMrr").textContent = fmtMoneyK(mrr);
$("#statSeats").textContent = seats.toLocaleString("en-US");
$("#statRisk").textContent = risk;
}
/* ---------- Detail drawer ---------- */
var lastFocus = null;
function openDrawer(id) {
var c = DATA.find(function (x) { return x.id === id; });
if (!c) return;
lastFocus = document.activeElement;
var hc = healthClass(c.health);
var usagePct = Math.min(100, Math.round(c.seats / Math.max(c.seats, 1) * 100));
var apiPct = Math.min(100, 30 + (c.health % 60));
var storPct = Math.min(100, 20 + (c.mrr % 70));
var inner = $("#drawerInner");
inner.innerHTML =
'<div class="dh">' +
'<span class="co-logo" style="background:' + logoColor(c.company) + '">' + initials(c.company) + "</span>" +
'<div class="dh-meta"><h2>' + esc(c.company) + "</h2>" +
'<div class="dh-sub">' + esc(c.owner) + " · " + esc(c.email) + "</div></div>" +
'<button class="dclose" id="drawerClose" aria-label="Close detail">×</button>' +
"</div>" +
'<div class="dgrid">' +
'<div class="dcell"><span>Plan</span><strong><span class="badge plan-' + c.plan + '">' + c.plan + "</span></strong></div>" +
'<div class="dcell"><span>Status</span><strong><span class="status ' + c.status + '">' + STATUS_LABEL[c.status] + "</span></strong></div>" +
'<div class="dcell"><span>MRR</span><strong>' + fmtMoney(c.mrr) + "</strong></div>" +
'<div class="dcell"><span>Seats</span><strong>' + c.seats + "</strong></div>" +
'<div class="dcell"><span>Health</span><strong class="' + hc.replace("h-", "health-num--") + '" style="color:' + (c.health >= 75 ? "var(--ok)" : c.health >= 50 ? "var(--warn)" : "var(--danger)") + '">' + c.health + " / 100</strong></div>" +
'<div class="dcell"><span>Customer since</span><strong>' + c.since + "</strong></div>" +
"</div>" +
'<div class="dsection"><h3>Usage this cycle</h3>' +
usageRow("Seats assigned", c.seats + " / " + (c.seats + 5), usagePct, usagePct > 85) +
usageRow("API calls", apiPct + "% of quota", apiPct, apiPct > 85) +
usageRow("Storage", storPct + "% of quota", storPct, storPct > 85) +
"</div>" +
'<div class="dsection"><h3>Recent activity</h3><ul class="activity">' +
actItem("ok", "Invoice paid · " + fmtMoney(c.mrr), "2 days ago") +
actItem("", c.owner + " invited 2 teammates", "5 days ago") +
actItem(c.health < 50 ? "warn" : "", c.health < 50 ? "Usage dropped 40% vs prior cycle" : "Upgraded API rate limit", "1 week ago") +
actItem("", "Logged in from " + c.region, "1 week ago") +
"</ul></div>" +
'<div class="dsection"><h3>Billing</h3>' +
billRow("Jun 2026", c.mrr, c.status === "past_due" ? "due" : "paid") +
billRow("May 2026", c.mrr, "paid") +
billRow("Apr 2026", c.mrr, "paid") +
"</div>" +
'<div class="dactions">' +
'<button class="btn btn-ghost" data-act="message">Message owner</button>' +
'<button class="btn btn-primary" data-act="manage">Manage plan</button>' +
"</div>";
var scrim = $("#scrim"), drawer = $("#drawer");
scrim.hidden = false;
drawer.setAttribute("aria-hidden", "false");
requestAnimationFrame(function () { scrim.classList.add("show"); drawer.classList.add("open"); });
$("#drawerClose").addEventListener("click", closeDrawer);
inner.querySelectorAll(".dactions .btn").forEach(function (b) {
b.addEventListener("click", function () {
toast(b.dataset.act === "manage" ? "Opening plan manager for " + c.company : "Composing message to " + c.owner);
});
});
drawer.focus();
}
function usageRow(label, val, pct, warn) {
return '<div class="usage-row"><div class="ur-top"><span>' + label + "</span><em>" + val + "</em></div>" +
'<div class="ubar' + (warn ? " warn" : "") + '"><i style="width:' + pct + '%"></i></div></div>';
}
function actItem(kind, text, time) {
return '<li><span class="act-dot ' + kind + '"></span><div class="act-body"><p>' + esc(text) + "</p><time>" + time + "</time></div></li>";
}
function billRow(month, amt, st) {
return '<div class="bill-row"><span>' + month + '</span><span class="bill-amt">' + fmtMoney(amt) +
'</span><span class="pill ' + st + '">' + (st === "due" ? "Past due" : "Paid") + "</span></div>";
}
function closeDrawer() {
var scrim = $("#scrim"), drawer = $("#drawer");
scrim.classList.remove("show");
drawer.classList.remove("open");
drawer.setAttribute("aria-hidden", "true");
setTimeout(function () { scrim.hidden = true; }, 220);
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
/* ---------- Export ---------- */
function exportCsv(rows, label) {
var head = ["Account ID", "Company", "Owner", "Email", "Plan", "MRR", "Seats", "Status", "Health"];
var lines = [head.join(",")];
rows.forEach(function (c) {
lines.push([c.id, '"' + c.company + '"', '"' + c.owner + '"', c.email, c.plan, c.mrr, c.seats, c.status, c.health].join(","));
});
var blob = new Blob([lines.join("\n")], { type: "text/csv" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url; a.download = "heliox-customers.csv";
document.body.appendChild(a); a.click(); a.remove();
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
toast("Exported " + rows.length + " " + label);
}
/* ---------- Sort header UI ---------- */
function syncSortUI() {
document.querySelectorAll(".sort").forEach(function (btn) {
var on = btn.dataset.key === state.sortKey;
btn.classList.toggle("is-sorted", on);
btn.classList.toggle("desc", on && state.sortDir === "desc");
btn.setAttribute("aria-sort", on ? (state.sortDir === "asc" ? "ascending" : "descending") : "none");
});
}
/* ---------- Events ---------- */
function wire() {
$("#search").addEventListener("input", function (e) { state.search = e.target.value; state.page = 1; render(); });
$("#planFilter").addEventListener("change", function (e) { state.plan = e.target.value; state.page = 1; render(); });
$("#statusFilter").addEventListener("change", function (e) { state.status = e.target.value; state.page = 1; render(); });
document.querySelectorAll(".sort").forEach(function (btn) {
btn.addEventListener("click", function () {
var k = btn.dataset.key;
if (state.sortKey === k) state.sortDir = state.sortDir === "asc" ? "desc" : "asc";
else { state.sortKey = k; state.sortDir = (k === "company" || k === "status" || k === "plan") ? "asc" : "desc"; }
syncSortUI(); render();
});
});
$("#rows").addEventListener("click", function (e) {
var tr = e.target.closest("tr");
if (!tr) return;
if (e.target.classList.contains("rowcheck")) {
toggleSelect(tr.dataset.id, e.target.checked);
return;
}
openDrawer(tr.dataset.id);
});
$("#checkAll").addEventListener("change", function (e) {
var list = filtered();
var start = (state.page - 1) * state.pageSize;
var pageItems = list.slice(start, start + state.pageSize);
pageItems.forEach(function (c) {
if (e.target.checked) state.selected.add(c.id); else state.selected.delete(c.id);
});
render();
});
$("#prevPage").addEventListener("click", function () { if (state.page > 1) { state.page--; render(); } });
$("#nextPage").addEventListener("click", function () { state.page++; render(); });
$("#exportBtn").addEventListener("click", function () { exportCsv(filtered(), "accounts"); });
$("#bulkExport").addEventListener("click", function () {
exportCsv(DATA.filter(function (c) { return state.selected.has(c.id); }), "selected accounts");
});
$("#bulkFlag").addEventListener("click", function () {
toast("Flagged " + state.selected.size + " account(s) for review");
});
$("#bulkClear").addEventListener("click", function () { state.selected.clear(); render(); });
$("#resetEmpty").addEventListener("click", resetFilters);
$("#scrim").addEventListener("click", closeDrawer);
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && $("#drawer").classList.contains("open")) closeDrawer();
});
$("#newBtn").addEventListener("click", function () { toast("New account form would open here"); });
var toggle = $("#themeToggle");
toggle.addEventListener("click", function () {
var dark = document.documentElement.getAttribute("data-theme") === "dark";
document.documentElement.setAttribute("data-theme", dark ? "light" : "dark");
toggle.setAttribute("aria-pressed", String(!dark));
$(".ico-sun", toggle).textContent = dark ? "☀" : "☾";
});
}
function toggleSelect(id, on) {
if (on) state.selected.add(id); else state.selected.delete(id);
var tr = document.querySelector('tr[data-id="' + id + '"]');
if (tr) tr.classList.toggle("is-selected", on);
renderBulk();
// sync check-all
var list = filtered();
var start = (state.page - 1) * state.pageSize;
var pageItems = list.slice(start, start + state.pageSize);
var allChecked = pageItems.length > 0 && pageItems.every(function (c) { return state.selected.has(c.id); });
$("#checkAll").checked = allChecked;
$("#checkAll").indeterminate = !allChecked && pageItems.some(function (c) { return state.selected.has(c.id); });
}
function resetFilters() {
state.search = ""; state.plan = ""; state.status = ""; state.page = 1;
$("#search").value = ""; $("#planFilter").value = ""; $("#statusFilter").value = "";
render();
}
/* ---------- Init ---------- */
renderStats();
wire();
syncSortUI();
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Customers · Heliox Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar" aria-label="Primary">
<div class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none"><path d="M12 2L3 7l9 5 9-5-9-5Z" fill="currentColor"/><path d="M3 12l9 5 9-5M3 17l9 5 9-5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="brand-name">Heliox</span>
<span class="brand-tag">Admin</span>
</div>
<nav class="nav" aria-label="Sections">
<a href="#" class="nav-item"><span class="dot" aria-hidden="true"></span>Overview</a>
<a href="#" class="nav-item is-active" aria-current="page"><span class="dot" aria-hidden="true"></span>Customers</a>
<a href="#" class="nav-item"><span class="dot" aria-hidden="true"></span>Subscriptions</a>
<a href="#" class="nav-item"><span class="dot" aria-hidden="true"></span>Invoices</a>
<a href="#" class="nav-item"><span class="dot" aria-hidden="true"></span>Usage</a>
<a href="#" class="nav-item"><span class="dot" aria-hidden="true"></span>Audit log</a>
</nav>
<div class="sidebar-foot">
<div class="who">
<span class="avatar" aria-hidden="true">RM</span>
<span class="who-meta"><strong>Rae Morgan</strong><small>Revenue Ops</small></span>
</div>
</div>
</aside>
<!-- Main -->
<main class="main" id="main">
<header class="topbar">
<div class="crumbs">
<span>Accounts</span><span aria-hidden="true">/</span><strong>Customers</strong>
</div>
<div class="topbar-actions">
<button class="btn btn-ghost" id="themeToggle" type="button" aria-pressed="false">
<span class="ico-sun" aria-hidden="true">☀</span><span class="lbl">Theme</span>
</button>
<button class="btn btn-primary" id="newBtn" type="button">+ New account</button>
</div>
</header>
<section class="page">
<div class="page-head">
<div>
<h1>Customers</h1>
<p class="sub">Manage accounts, plans, seats, and account health.</p>
</div>
</div>
<!-- Stat strip -->
<div class="stats" role="list">
<div class="stat" role="listitem">
<span class="stat-label">Accounts</span>
<strong class="stat-val" id="statAccounts">—</strong>
<span class="stat-trend up">live</span>
</div>
<div class="stat" role="listitem">
<span class="stat-label">Total MRR</span>
<strong class="stat-val" id="statMrr">—</strong>
<span class="stat-trend up">▲ 6.4%</span>
</div>
<div class="stat" role="listitem">
<span class="stat-label">Active seats</span>
<strong class="stat-val" id="statSeats">—</strong>
<span class="stat-trend up">▲ 3.1%</span>
</div>
<div class="stat" role="listitem">
<span class="stat-label">At-risk</span>
<strong class="stat-val" id="statRisk">—</strong>
<span class="stat-trend down">needs review</span>
</div>
</div>
<!-- Toolbar -->
<div class="toolbar" role="region" aria-label="Filters">
<div class="search">
<span class="search-ico" aria-hidden="true">⌕</span>
<input id="search" type="search" placeholder="Search company or owner…" aria-label="Search customers" autocomplete="off" />
</div>
<label class="select">
<span class="vh">Plan</span>
<select id="planFilter" aria-label="Filter by plan">
<option value="">All plans</option>
<option value="Free">Free</option>
<option value="Starter">Starter</option>
<option value="Growth">Growth</option>
<option value="Scale">Scale</option>
<option value="Enterprise">Enterprise</option>
</select>
</label>
<label class="select">
<span class="vh">Status</span>
<select id="statusFilter" aria-label="Filter by status">
<option value="">Any status</option>
<option value="active">Active</option>
<option value="trialing">Trialing</option>
<option value="past_due">Past due</option>
<option value="churned">Churned</option>
</select>
</label>
<button class="btn btn-ghost" id="exportBtn" type="button">⭳ Export</button>
</div>
<!-- Bulk bar -->
<div class="bulkbar" id="bulkbar" hidden role="region" aria-label="Bulk actions">
<span><strong id="bulkCount">0</strong> selected</span>
<button class="btn btn-mini" id="bulkExport" type="button">Export selected</button>
<button class="btn btn-mini btn-warn" id="bulkFlag" type="button">Flag for review</button>
<button class="btn btn-mini btn-link" id="bulkClear" type="button">Clear</button>
</div>
<!-- Table -->
<div class="table-wrap">
<table class="grid" aria-label="Customer accounts">
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="checkAll" aria-label="Select all rows" /></th>
<th><button class="sort" data-key="company" type="button">Company <span class="caret" aria-hidden="true"></span></button></th>
<th><button class="sort" data-key="plan" type="button">Plan <span class="caret" aria-hidden="true"></span></button></th>
<th class="num"><button class="sort" data-key="mrr" type="button">MRR <span class="caret" aria-hidden="true"></span></button></th>
<th class="num"><button class="sort" data-key="seats" type="button">Seats <span class="caret" aria-hidden="true"></span></button></th>
<th><button class="sort" data-key="status" type="button">Status <span class="caret" aria-hidden="true"></span></button></th>
<th><button class="sort" data-key="health" type="button">Health <span class="caret" aria-hidden="true"></span></button></th>
<th class="col-act" aria-label="Actions"></th>
</tr>
</thead>
<tbody id="rows"><!-- rows injected --></tbody>
</table>
<div class="empty" id="empty" hidden>
<span class="empty-ico" aria-hidden="true">⌕</span>
<p><strong>No accounts match</strong><br/>Try clearing a filter or searching a different name.</p>
<button class="btn btn-ghost" id="resetEmpty" type="button">Reset filters</button>
</div>
</div>
<!-- Footer / pagination -->
<div class="pager">
<span class="pager-info" id="pagerInfo">—</span>
<div class="pager-ctl">
<button class="btn btn-ghost" id="prevPage" type="button" disabled>‹ Prev</button>
<span class="pager-pages" id="pagerPages"></span>
<button class="btn btn-ghost" id="nextPage" type="button">Next ›</button>
</div>
</div>
</section>
</main>
</div>
<!-- Detail drawer -->
<div class="scrim" id="scrim" hidden></div>
<aside class="drawer" id="drawer" aria-label="Customer detail" aria-hidden="true" tabindex="-1">
<div class="drawer-inner" id="drawerInner"><!-- detail injected --></div>
</aside>
<div class="toast-wrap" id="toasts" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Customers / Accounts Admin
An internal revenue-ops view for managing SaaS accounts. The accounts table packs company, owner, plan, MRR, seats, status, and a color-coded health bar into an information-dense but readable grid. A sticky toolbar offers live search across company, owner, email, and account ID, plus plan and status dropdowns. Every column header is a sort toggle that cycles ascending and descending, and the result set paginates with a numbered pager.
Clicking a row opens a slide-in detail drawer with the full account picture: plan and status at a glance, a usage section with seat, API, and storage meters, a recent-activity feed, and three months of billing history with paid and past-due pills. Checkbox selection drives a bulk-action bar where you can export the chosen accounts, flag them for review, or clear the selection.
Everything runs on vanilla JS with no dependencies. CSV export builds a real downloadable file in the browser, the theme toggle flips a polished light and dark palette, and the empty state appears with a reset action when filters match nothing. The layout collapses gracefully toward mobile, dropping the densest columns first.
Illustrative SaaS UI only — fictional product, metrics, and billing. No real backend.