eleven / messenger Public

home: Manage sits beside the tier badge, not in the Upgrade slot (#691)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 5163e7f06a2f38a7d05592f434ff69f54e04e773 parent d430554
2 files changed, +22 −14
  • home/web/pages/index.html +10 −2
  • home/web/static/home.js +12 −12
diff --git a/home/web/pages/index.html b/home/web/pages/index.html
index 3735aaa..f8d73e5 100644
--- a/home/web/pages/index.html
+++ b/home/web/pages/index.html
@@ -244,9 +244,17 @@
.tier-btn:disabled { opacity: .5; cursor: default; }
.tier-btn .tier-name { font-weight: 700; }
.tier-btn .tier-price { color: var(--muted); font-size: .9rem; }
- /* The tier badge on an upgraded space's row. */
+ /* The tier badge on an upgraded space's row, with Manage beside it. */
+ .badge-row { display: inline-flex; align-items: center; gap: 8px; align-self: flex-start; margin-top: 2px; }
+ .row-manage {
+ font: inherit; font-size: .62rem; font-weight: 600; cursor: pointer;
+ min-height: 24px; padding: 1px 8px; border-radius: 999px;
+ color: var(--muted); background: none; border: 1px solid var(--border);
+ }
+ .row-manage:hover { color: var(--accent); border-color: var(--accent); }
+ .row-manage:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
+ .row-manage:disabled { opacity: .4; cursor: default; }
.supporter-badge {
- align-self: flex-start; margin-top: 2px;
font-size: .62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
border-radius: 999px; padding: 1px 7px;
diff --git a/home/web/static/home.js b/home/web/static/home.js
index 8e07402..0fe36cf 100644
--- a/home/web/static/home.js
+++ b/home/web/static/home.js
@@ -464,10 +464,21 @@ function serverRow(s, sortable) {
// upgraded rows carry a quiet Supporter badge; free ones offer Upgrade.
const ledger = billingEnabled && provisioned ? provisioned[hostOf(s.url)] : null;
if (ledger && ledger.entitlement === "paid") {
+ // The tier badge with Manage right beside it — the subscription's label
+ // and its controls read as one unit.
+ const row = document.createElement("span");
+ row.className = "badge-row";
const badge = document.createElement("span");
badge.className = "supporter-badge";
badge.textContent = tierName(ledger.tier);
- main.append(badge);
+ const manage = document.createElement("button");
+ manage.type = "button";
+ manage.className = "row-manage";
+ manage.textContent = "Manage";
+ manage.setAttribute("aria-label", "Manage the subscription for " + labelOf(s));
+ manage.addEventListener("click", () => openPortal(hostOf(s.url), manage));
+ row.append(badge, manage);
+ main.append(row);
}
// The storage meter's reading (spaces this account created; 0 = not yet
@@ -492,17 +503,6 @@ function serverRow(s, sortable) {
up.setAttribute("aria-label", "Upgrade " + labelOf(s));
up.addEventListener("click", () => openUpgrade(s));
li.append(up);
- } else if (ledger && ledger.entitlement === "paid") {
- // Manage the subscription behind this space — Stripe's hosted Billing
- // Portal (cancel, change card, invoices). Per-space by design: each
- // space is its own Stripe customer, so there's no cross-space portal.
- const manage = document.createElement("button");
- manage.type = "button";
- manage.className = "row-upgrade";
- manage.textContent = "Manage";
- manage.setAttribute("aria-label", "Manage the subscription for " + labelOf(s));
- manage.addEventListener("click", () => openPortal(hostOf(s.url), manage));
- li.append(manage);
}
// Archive: a quiet box glyph that reveals on hover (like the grip). One tap