UX polish — duration, preview button, Select state, dropdown current, switch action

Pure UX bundle from the testing batch. None individually changes
behavior; together they remove a half-dozen sharp edges.

1. Policy-list duration column: human-readable
   `31536000s` / `604800s` / `0s` are now `1 year` / `1 week` /
   `perpetual`. New `fmtDuration()` helper handles common cadences
   (1 day, 1 week, 1 month, 3 months, 6 months, 1 year, 2 years)
   with arithmetic fallbacks for non-canonical values. Grace
   column gets the same treatment with "none" for 0.

2. "Preview buy page" button per product header
   The Policies tab's per-product card now has a "Preview buy
   page" button on the right side of the header (when ≥ 1
   public+active policy exists). Opens /buy/<slug> in a new
   tab. tableCard() helper grew an optional headerAction param.

3. Buy page tier card: "Select" → "Selected"
   When a tier becomes the active selection, its button label
   flips to "Selected" while other tiers' buttons stay "Select".
   Combined with the existing .selected card-border styling
   gives buyers an unambiguous "yes, this tier is what's tied
   to the price card below" cue.

4. Licenses page POLICY column shows display name
   Was showing slug (`recurring`, `core`, `creator`); now shows
   the operator-set display name (Recurring Pro, Core, Creator)
   primary, with the slug as a smaller mono-font line below.
   Operators see what the buyer sees while keeping the slug
   visible for SDK reference. (Subscriptions tab already
   handled this pattern; this brings Licenses in line.)

5. Change Tier dropdown: "(current)" annotation
   Current tier now appears in the dropdown but with " · current"
   appended and `disabled` attribute set. Operator sees what
   they're starting from but can't pick the no-op. Auto-selects
   the first SELECTABLE option so the modal opens with a valid
   target ready. formSelect() helper grew per-option `disabled`
   support.

6. Single "Switch active payment provider" StartOS action
   The two old "Activate BTCPay" / "Activate Zaprite" actions
   collapsed into one dropdown-driven action. Operators saw the
   pair as confusing — both appeared alongside Connect /
   Disconnect / Status, and operators couldn't tell at a glance
   which one was currently active. New action pre-fills the
   dropdown with the currently-active provider so opening it is
   immediately informative.
   Old action ids retained as visibility:'hidden' shims for
   back-compat with any operator scripts pointing at them.

Test count unchanged; UI-only changes don't touch any test
fixtures.
This commit is contained in:
Grant
2026-05-09 14:02:20 -05:00
parent 54f7ea08b5
commit 927ac2be53
4 changed files with 193 additions and 45 deletions
+5 -3
View File
@@ -20,7 +20,7 @@
import { sdk } from '../sdk'
import { activateLicense, showLicenseStatus } from './activateLicense'
import { activateBtcpay, activateZaprite } from './activatePaymentProvider'
import { switchPaymentProvider } from './activatePaymentProvider'
import { btcpayStatus, configureBtcpay, disconnectBtcpay } from './configureBtcpay'
import {
configureZaprite,
@@ -39,14 +39,16 @@ export const actions = sdk.Actions.of()
// BTCPay setup (Bitcoin-only payments via your own BTCPay Server)
.addAction(configureBtcpay)
.addAction(btcpayStatus)
.addAction(activateBtcpay)
.addAction(disconnectBtcpay)
// Zaprite setup (Bitcoin + fiat-card payments via Zaprite's broker)
.addAction(configureZaprite)
.addAction(zapriteStatus)
.addAction(showZapriteWebhookSetup)
.addAction(activateZaprite)
.addAction(disconnectZaprite)
// Single unified switch action — flips active provider via a
// dropdown so operators don't see two confusing "Activate X"
// actions side-by-side, each appearing to override the other.
.addAction(switchPaymentProvider)
// Keysat self-license (Keysat-licenses-Keysat)
.addAction(activateLicense)
.addAction(showLicenseStatus)