Admin settings: graphical editors for follow-up emails, rates and site settings

Follow-ups: one card per email (hours, subject, body), placeholder chips,
add / reorder / remove / send-to-me. Rates: labeled fields with hints, tier
table, milestone sub-fields. Site settings: key/value rows. No more JSON.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-09 10:00:27 -05:00
parent 9b32d40874
commit dc3a8e0464
2 changed files with 216 additions and 46 deletions
+51 -15
View File
@@ -21,6 +21,35 @@
.adm-form label.small{display:block;margin-bottom:4px}
.trunc{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:bottom}
textarea.json{font-family:var(--mono);font-size:12.5px;width:100%;min-height:260px}
.ph-chips{display:inline-flex;gap:6px;flex-wrap:wrap;margin-left:6px;vertical-align:middle}
.ph-chips .chip-t{padding:2px 10px;font-size:11.5px;font-family:var(--mono);text-transform:none;letter-spacing:0;cursor:pointer;background:transparent}
.drip-steps{display:grid;gap:12px}
.drip-step{border:1px solid var(--line);border-radius:12px;padding:14px 16px;background:rgba(4,8,7,.4)}
.drip-step .ds-head{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:8px}
.drip-step .ds-n{font-family:var(--mono);font-size:12px;color:var(--mint);letter-spacing:.08em}
.drip-step .ds-when{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--muted)}
.drip-step .ds-when input{width:80px;text-align:right}
.drip-step .ds-when b{color:var(--ink)}
.drip-step .ds-tools{margin-left:auto;display:flex;gap:6px;flex-wrap:wrap}
.drip-step .ds-tools .btn{padding:6px 12px;font-size:12px}
.drip-step input.ds-subject{width:100%;margin-bottom:8px;font-weight:700}
.drip-step textarea.ds-body{width:100%;min-height:200px;font-size:13.5px;line-height:1.5}
.rates-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px 22px}
.rates-form .rf{display:flex;flex-direction:column;gap:4px}
.rates-form .rf label{font-size:13px;font-weight:700}
.rates-form .rf .hint{font-size:11.5px;color:var(--muted)}
.rates-form .rf input[type=number]{max-width:160px}
.rates-form .rf.wide{grid-column:1/-1}
.rates-form .sub-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px}
.rates-form .sub-grid label{font-size:12px;font-weight:600;color:var(--muted);display:flex;flex-direction:column;gap:3px}
.rates-form table.tiers{width:auto;font-size:13px}
.rates-form table.tiers td,.rates-form table.tiers th{padding:4px 8px;border:0}
.rates-form table.tiers input{width:90px}
.kv-form{display:grid;gap:8px}
.kv-row{display:grid;grid-template-columns:220px 1fr auto;gap:10px;align-items:center}
.kv-row .k{font-family:var(--mono);font-size:12.5px;overflow-wrap:anywhere}
.kv-row input[type=checkbox]{width:auto;justify-self:start}
@media(max-width:700px){.kv-row{grid-template-columns:1fr}}
</style>
</head>
<body class="bo-body">
@@ -203,30 +232,37 @@
<div class="pane" id="pane-settings" hidden>
<div class="card">
<div class="card-head"><h3>Follow-up emails</h3><span class="sub" id="dripSub">sent to every new free member, hours after sign-up</span></div>
<p class="small muted">Each step: <code>hours</code> after the account was created, a <code>subject</code>, and a plain-text <code>body</code>.
Placeholders: <code>{{link}}</code> the member's invite link, <code>{{sponsor}}</code>, <code>{{site}}</code>, <code>{{footer}}</code> (unsubscribe line + disclaimer, keep it).
Steps must be in increasing hours.</p>
<p><textarea id="dripJson" class="json" style="min-height:360px"></textarea></p>
<p class="small muted">One card per email. Placeholders fill in per reader: click a chip to drop it where your cursor is.
<span class="ph-chips" id="phChips">
<button type="button" class="chip-t" data-ph="{{link}}" title="the reader's own invite link">{{link}}</button>
<button type="button" class="chip-t" data-ph="{{sponsor}}" title="their sponsor's name">{{sponsor}}</button>
<button type="button" class="chip-t" data-ph="{{site}}" title="https://instantadpay.com">{{site}}</button>
<button type="button" class="chip-t" data-ph="{{paid:words if they bought|words if they have not}}" title="two versions: bought / not yet">{{paid:…|…}}</button>
<button type="button" class="chip-t" data-ph="{{footer}}" title="unsubscribe line + disclaimer (keep it at the end)">{{footer}}</button>
</span></p>
<div id="dripSteps" class="drip-steps"></div>
<p id="dripErr" class="small" style="color:var(--bad)" hidden></p>
<p style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:0">
<p style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:14px 0 0">
<button class="btn small" id="dripSave" type="button">Save sequence</button>
<button class="btn small sec" id="dripAdd" type="button">+ Add an email</button>
<button class="btn small sec" id="dripReset" type="button">Reset to defaults</button>
<span style="flex:1"></span>
<select id="dripTestStep" style="width:auto"></select>
<button class="btn small sec" id="dripTest" type="button">Send that step to me</button>
</p>
</div>
<div class="card">
<div class="card-head"><h3>Rates</h3><span class="sub">credit prices and rewards (JSON, merged on save)</span></div>
<p><textarea id="ratesJson" class="json"></textarea></p>
<div class="card-head"><h3>Rates</h3><span class="sub">credit prices, rewards and limits</span></div>
<div id="ratesForm" class="rates-form"></div>
<p id="ratesErr" class="small" style="color:var(--bad)" hidden></p>
<button class="btn small" id="ratesSave" type="button">Save rates</button>
<p style="margin:14px 0 0"><button class="btn small" id="ratesSave" type="button">Save rates</button></p>
</div>
<div class="card">
<div class="card-head"><h3>Site config</h3><span class="sub">public: everything here is served by /api/config, never put secrets in it</span></div>
<p><textarea id="siteJson" class="json"></textarea></p>
<div class="card-head"><h3>Site settings</h3><span class="sub">public: everything here is served to the browser, never put a secret in it</span></div>
<div id="siteForm" class="kv-form"></div>
<p style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:12px 0 0">
<input id="siteNewKey" placeholder="new setting name" style="max-width:220px">
<button class="btn small sec" id="siteAddKey" type="button">+ Add setting</button>
</p>
<p id="siteErr" class="small" style="color:var(--bad)" hidden></p>
<button class="btn small" id="siteSave" type="button">Save site config</button>
<p style="margin:14px 0 0"><button class="btn small" id="siteSave" type="button">Save site settings</button></p>
</div>
</div>
@@ -235,6 +271,6 @@
</div>
<script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/admin.js?v=20260909b"></script>
<script src="/assets/admin.js?v=20260909c"></script>
</body>
</html>