Some checks are pending
offline / test (push) Waiting to run
Match CS/sales/accounting/access-staff to the portal indigo system with dollar amounts, skip links, and empty states. Fleet replica actions move into overflow menus, roles become chips, Docs become cards, and the header copy reflects the 0.0.0.0 bind. Simulator uses the same shell (orange only for faults). Portal API keys are masked; admin customers edit in a drawer. Catalog uses system-ui. New UI-Docs repo holds screenshots, usage notes, and UI-REVIEW.pdf.
282 lines
12 KiB
HTML
282 lines
12 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Zappier Portal</title>
|
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%234f46e5'/%3E%3C/svg%3E"/>
|
|
<style>
|
|
:root {
|
|
--bg: #f4f5fb;
|
|
--panel: #ffffff;
|
|
--ink: #171a26;
|
|
--muted: #6b7186;
|
|
--line: #e5e7f0;
|
|
--accent: #4f46e5;
|
|
--accent-ink: #ffffff;
|
|
--accent-soft: #eef0fe;
|
|
--danger: #dc2626;
|
|
--ok: #047857;
|
|
--radius: 12px;
|
|
--shadow: 0 1px 2px rgba(23, 26, 38, 0.05), 0 8px 24px rgba(23, 26, 38, 0.06);
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
margin: 0;
|
|
font-family: -apple-system, "SF Pro Text", "Segoe UI", "PingFang SC", sans-serif;
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* ---------- Auth ---------- */
|
|
#auth {
|
|
min-height: 100vh;
|
|
display: grid;
|
|
place-items: center;
|
|
background: linear-gradient(160deg, #312e81 0%, #4f46e5 55%, #7c74f0 100%);
|
|
}
|
|
#auth .card {
|
|
width: 380px;
|
|
background: var(--panel);
|
|
border-radius: 16px;
|
|
box-shadow: 0 24px 64px rgba(17, 12, 60, 0.35);
|
|
padding: 2rem;
|
|
}
|
|
#auth h1 { font-size: 1.35rem; margin: 0 0 0.25rem; }
|
|
#auth p.sub { color: var(--muted); margin: 0 0 1.25rem; }
|
|
#auth label { display: block; font-weight: 600; font-size: 0.8rem; margin: 0.9rem 0 0.3rem; }
|
|
#auth input {
|
|
width: 100%;
|
|
padding: 0.6rem 0.75rem;
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
font-size: 0.95rem;
|
|
}
|
|
#auth input:focus { outline: 2px solid var(--accent); border-color: transparent; }
|
|
#auth button.primary {
|
|
width: 100%;
|
|
margin-top: 1.4rem;
|
|
padding: 0.65rem;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
background: var(--accent);
|
|
color: var(--accent-ink);
|
|
font-weight: 700;
|
|
font-size: 0.95rem;
|
|
cursor: pointer;
|
|
}
|
|
#auth button.primary:hover { filter: brightness(1.08); }
|
|
#auth-error { color: var(--danger); font-size: 0.85rem; min-height: 1.2em; margin: 0.6rem 0 0; }
|
|
#auth-switch { margin: 1rem 0 0; font-size: 0.85rem; color: var(--muted); text-align: center; }
|
|
#auth-switch a { color: var(--accent); cursor: pointer; font-weight: 600; text-decoration: none; }
|
|
#totp-group { display: none; }
|
|
|
|
/* ---------- Shell ---------- */
|
|
#shell { display: none; min-height: 100vh; }
|
|
#shell.on { display: grid; grid-template-columns: 232px 1fr; }
|
|
aside {
|
|
background: var(--panel);
|
|
border-right: 1px solid var(--line);
|
|
padding: 1.25rem 0.9rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
position: sticky;
|
|
top: 0;
|
|
height: 100vh;
|
|
}
|
|
.brand { display: flex; align-items: center; gap: 0.6rem; padding: 0.25rem 0.6rem 1.1rem; }
|
|
.brand .dot {
|
|
width: 30px; height: 30px; border-radius: 9px;
|
|
background: linear-gradient(140deg, var(--accent), #8b85f2);
|
|
display: grid; place-items: center; color: #fff; font-weight: 800;
|
|
}
|
|
.brand b { font-size: 1.02rem; }
|
|
nav button {
|
|
display: flex; align-items: center; gap: 0.55rem;
|
|
width: 100%;
|
|
border: 0; background: none;
|
|
text-align: left;
|
|
padding: 0.55rem 0.7rem;
|
|
border-radius: 8px;
|
|
font-size: 0.92rem;
|
|
color: var(--muted);
|
|
cursor: pointer;
|
|
}
|
|
nav button { min-height: 40px; }
|
|
nav button:hover { background: var(--bg); color: var(--ink); }
|
|
nav button.active { background: var(--accent-soft); color: var(--accent); font-weight: 700; }
|
|
nav button:focus-visible, #logout:focus-visible, button.btn:focus-visible, a:focus-visible {
|
|
outline: 2px solid var(--accent); outline-offset: 2px;
|
|
}
|
|
.skip { position:absolute; left:-999px; }
|
|
.skip:focus { left:1rem; top:1rem; z-index:30; background:#fff; color:var(--accent); padding:.5rem .9rem; border-radius:8px; }
|
|
aside .spacer { flex: 1; }
|
|
#logout {
|
|
border: 1px solid var(--line); background: none; border-radius: 8px;
|
|
padding: 0.5rem; color: var(--muted); cursor: pointer; font-size: 0.85rem;
|
|
}
|
|
#logout:hover { color: var(--danger); border-color: var(--danger); }
|
|
|
|
main { padding: 1.75rem 2rem 3rem; max-width: 1080px; }
|
|
main h2 { margin: 0 0 0.25rem; font-size: 1.3rem; }
|
|
main .lede { color: var(--muted); margin: 0 0 1.25rem; }
|
|
|
|
.card {
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
padding: 1.1rem 1.25rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.card h3 { margin: 0 0 0.9rem; font-size: 0.95rem; }
|
|
|
|
table { border-collapse: collapse; width: 100%; }
|
|
th {
|
|
text-align: left; font-size: 0.72rem; text-transform: uppercase;
|
|
letter-spacing: 0.04em; color: var(--muted);
|
|
border-bottom: 1px solid var(--line); padding: 0.45rem 0.6rem;
|
|
}
|
|
td { border-bottom: 1px solid var(--line); padding: 0.55rem 0.6rem; }
|
|
tr:last-child td { border-bottom: 0; }
|
|
tbody tr:hover { background: #fafaff; }
|
|
td.id, .id { font-family: "SF Mono", Menlo, monospace; font-size: 0.82rem; font-weight: 600; }
|
|
|
|
input, select {
|
|
padding: 0.4rem 0.55rem;
|
|
border: 1px solid var(--line);
|
|
border-radius: 7px;
|
|
font-size: 0.88rem;
|
|
background: #fff;
|
|
color: var(--ink);
|
|
}
|
|
input:focus, select:focus { outline: 2px solid var(--accent); border-color: transparent; }
|
|
|
|
button.btn {
|
|
border: 0; border-radius: 7px; padding: 0.42rem 0.85rem;
|
|
font-size: 0.85rem; font-weight: 600; cursor: pointer;
|
|
background: var(--accent); color: var(--accent-ink);
|
|
}
|
|
button.btn:hover { filter: brightness(1.08); }
|
|
button.btn.ghost { background: none; border: 1px solid var(--line); color: var(--muted); }
|
|
button.btn.ghost:hover { color: var(--accent); border-color: var(--accent); }
|
|
.row-actions { white-space: nowrap; text-align: right; }
|
|
.row-actions button { margin-left: 0.35rem; }
|
|
|
|
.pill {
|
|
display: inline-block; padding: 0.1rem 0.55rem; border-radius: 999px;
|
|
font-size: 0.72rem; font-weight: 700;
|
|
}
|
|
.pill.draft { background: #f1f5f9; color: #475569; }
|
|
.pill.issued { background: #fff7ed; color: #c2410c; }
|
|
.pill.paid { background: #ecfdf5; color: var(--ok); }
|
|
.pill.free { background: #ecfdf5; color: var(--ok); }
|
|
.pill.fixed { background: var(--accent-soft); color: var(--accent); }
|
|
.pill.variable { background: #fff7ed; color: #c2410c; }
|
|
.pill.on { background: #ecfdf5; color: var(--ok); }
|
|
.pill.off { background: #f1f5f9; color: #475569; }
|
|
|
|
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.9rem; margin-bottom: 1.25rem; }
|
|
.stat {
|
|
background: var(--panel); border: 1px solid var(--line);
|
|
border-radius: var(--radius); box-shadow: var(--shadow);
|
|
padding: 0.9rem 1.1rem;
|
|
}
|
|
.stat .k { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
|
|
.stat .v { font-size: 1.35rem; font-weight: 800; margin-top: 0.2rem; font-variant-numeric: tabular-nums; }
|
|
|
|
.money { font-variant-numeric: tabular-nums; text-align: right; }
|
|
.kv { display: grid; grid-template-columns: 170px 1fr; row-gap: 0.5rem; align-items: center; }
|
|
.kv dt { color: var(--muted); font-size: 0.82rem; }
|
|
.kv dd { margin: 0; font-weight: 600; }
|
|
.apikey-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
|
.apikey-row code {
|
|
background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
|
|
padding: 0.45rem 0.7rem; font-size: 0.85rem; flex: 1; min-width: 12rem;
|
|
font-family: "SF Mono", Menlo, monospace; letter-spacing: 0.04em;
|
|
}
|
|
.empty { text-align:center; padding:1.6rem 1rem; color:var(--muted); }
|
|
.empty svg { display:block; margin:0 auto .6rem; }
|
|
.empty h3 { margin:0 0 .25rem; color:var(--ink); font-size:1rem; }
|
|
.qr-box { display: flex; gap: 1.25rem; align-items: flex-start; margin: 0.8rem 0; }
|
|
.qr-box img { border: 1px solid var(--line); border-radius: 10px; }
|
|
.qr-box .secret {
|
|
font-family: "SF Mono", Menlo, monospace; font-size: 0.9rem; font-weight: 700;
|
|
letter-spacing: 0.06em; background: var(--bg); padding: 0.4rem 0.7rem; border-radius: 7px;
|
|
}
|
|
.hint { color: var(--muted); font-size: 0.82rem; margin-top: 0.8rem; }
|
|
.filterbar { display: flex; flex-wrap: wrap; align-items: end; gap: 0.8rem; }
|
|
.filterbar label { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
.filterbar label span {
|
|
font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
|
|
color: var(--muted); font-weight: 600;
|
|
}
|
|
|
|
#status {
|
|
position: fixed; right: 1.25rem; bottom: 1.25rem;
|
|
background: var(--ink); color: #fff;
|
|
padding: 0.7rem 1.1rem; border-radius: 10px;
|
|
box-shadow: var(--shadow);
|
|
font-size: 0.88rem;
|
|
opacity: 0; transform: translateY(8px);
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
max-width: 420px;
|
|
pointer-events: none;
|
|
}
|
|
#status.show { opacity: 1; transform: none; }
|
|
#status.error { background: var(--danger); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<a class="skip" href="#main">Skip to content</a>
|
|
<section id="auth">
|
|
<form class="card" id="auth-form">
|
|
<h1>Zappier Portal</h1>
|
|
<p class="sub" id="auth-sub">Sign in to your customer account.</p>
|
|
<div id="name-group" style="display:none">
|
|
<label for="auth-name">Name</label>
|
|
<input id="auth-name" autocomplete="name" />
|
|
</div>
|
|
<label for="auth-email">Email</label>
|
|
<input id="auth-email" type="email" autocomplete="email" required />
|
|
<label for="auth-password">Password</label>
|
|
<input id="auth-password" type="password" autocomplete="current-password" required />
|
|
<div id="totp-group">
|
|
<label for="auth-totp">Authenticator code</label>
|
|
<input id="auth-totp" inputmode="numeric" placeholder="123456" />
|
|
</div>
|
|
<p id="auth-error"></p>
|
|
<button type="submit" class="primary" id="auth-submit">Sign in</button>
|
|
<p id="auth-switch">New here? <a id="auth-toggle">Create an account</a></p>
|
|
</form>
|
|
</section>
|
|
|
|
<div id="shell">
|
|
<aside>
|
|
<div class="brand"><span class="dot">Z</span><b>Zappier</b></div>
|
|
<nav>
|
|
<button data-tab="dashboard" class="active">▤ Dashboard</button>
|
|
<button data-tab="invoices">▦ Invoices</button>
|
|
<button data-tab="statement">☰ Statement</button>
|
|
<button data-tab="billing">↗ Billing</button>
|
|
<button data-tab="security">◈ Security</button>
|
|
<button data-tab="docs">▤ API & pricing</button>
|
|
</nav>
|
|
<div class="spacer"></div>
|
|
<button id="logout">Sign out</button>
|
|
</aside>
|
|
<main id="main">
|
|
<section id="dashboard"></section>
|
|
<section id="invoices" hidden></section>
|
|
<section id="statement" hidden></section>
|
|
<section id="billing" hidden></section>
|
|
<section id="security" hidden></section>
|
|
<section id="docs" hidden></section>
|
|
</main>
|
|
</div>
|
|
<p id="status"></p>
|
|
<script src="app.js"></script>
|
|
</body>
|
|
</html>
|