Skip to content

Commit 397e460

Browse files
Dashboard: per-card Delete button; 403 shows upgrade modal for free users
1 parent c52a43a commit 397e460

1 file changed

Lines changed: 61 additions & 1 deletion

File tree

dashboard.html

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@
4444
.small-btn.secondary { background: transparent; color: #888; border-color: #222; }
4545
.small-btn.secondary:hover { border-color: #4af; color: #4af; background: transparent; }
4646
.small-btn.copied { background: #4a4; border-color: #4a4; color: #e6ffe6; }
47+
.small-btn.danger { background: transparent; color: #f88; border: 1px solid #4a1818; }
48+
.small-btn.danger:hover:not(:disabled) { background: #2a1010; border-color: #6a2626; color: #fbb; }
49+
.upgrade-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 20px; }
50+
.upgrade-modal { background: #111; border: 1px solid #2a4a6a; border-radius: 10px; padding: 24px 28px; max-width: 420px; color: #e0e0e0; box-shadow: 0 10px 40px rgba(74,175,255,0.1); }
51+
.upgrade-modal h3 { color: #fff; margin-bottom: 8px; }
52+
.upgrade-modal p { color: #bbb; font-size: 0.92rem; margin-bottom: 18px; }
53+
.upgrade-actions { display: flex; gap: 10px; justify-content: flex-end; }
54+
.upgrade-actions a, .upgrade-actions button { text-decoration: none; }
4755
.footer-row { margin-top: 10px; display: flex; gap: 12px; flex-wrap: wrap; font-size: 0.75rem; color: #666; }
4856
.empty, .error { background: #111; border: 1px solid #222; border-radius: 8px; padding: 32px 20px; text-align: center; color: #888; }
4957
.empty code { display: block; margin-top: 12px; padding: 10px 14px; background: #0a0a0a; border: 1px solid #1a1a1a; border-radius: 6px; color: #adf; font-family: "SF Mono", monospace; font-size: 0.82rem; overflow-x: auto; }
@@ -197,7 +205,8 @@ <h2 id="apikey-heading">API token for CLI / agent</h2>
197205
var id = esc(r.id || r.token || '');
198206
var statusClass = esc((r.status || '').toLowerCase());
199207
var tierClass = (r.tier || '').toLowerCase() === 'paid' ? 'paid' : 'anonymous';
200-
return '<div class="card" data-id="' + id + '">' +
208+
var tokenAttr = esc(r.token || '');
209+
return '<div class="card" data-id="' + id + '" data-token="' + tokenAttr + '">' +
201210
'<div class="card-head">' +
202211
'<span class="badge ' + badge + '">' + esc(typeLabel(type)) + '</span>' +
203212
'<span class="pill ' + tierClass + '">' + esc(r.tier || 'unknown') + '</span>' +
@@ -208,6 +217,7 @@ <h2 id="apikey-heading">API token for CLI / agent</h2>
208217
'<code class="conn" data-full="' + esc(url) + '" data-masked="1">' + esc(mask(url) || '—') + '</code>' +
209218
(url ? '<button type="button" class="small-btn secondary js-show">Show</button>' : '') +
210219
(url ? '<button type="button" class="small-btn js-copy">Copy</button>' : '') +
220+
'<button type="button" class="small-btn danger js-delete" aria-label="Delete resource">Delete</button>' +
211221
'</div>' +
212222
'<div class="footer-row">' +
213223
'<span>Created ' + esc(relTime(r.created_at) || '—') + '</span>' +
@@ -241,8 +251,58 @@ <h2 id="apikey-heading">API token for CLI / agent</h2>
241251
navigator.clipboard.writeText(full).then(done, function () { fallbackCopy(full); done(); });
242252
} else { fallbackCopy(full); done(); }
243253
});
254+
var deleteBtn = card.querySelector('.js-delete');
255+
if (deleteBtn) deleteBtn.addEventListener('click', function () {
256+
var token = card.getAttribute('data-token');
257+
if (!token) return;
258+
if (!confirm('Permanently delete this resource? Any data inside is lost immediately.')) return;
259+
deleteBtn.disabled = true;
260+
deleteBtn.textContent = 'Deleting…';
261+
fetch(API + '/api/me/resources/' + encodeURIComponent(token), {
262+
method: 'DELETE',
263+
credentials: 'include'
264+
}).then(function (res) {
265+
return res.json().then(function (body) { return { status: res.status, body: body }; });
266+
}).then(function (r) {
267+
if (r.status === 202 || r.status === 200) {
268+
card.style.transition = 'opacity 0.2s';
269+
card.style.opacity = '0';
270+
setTimeout(function () { card.remove(); }, 220);
271+
toast('Queued for deletion. Underlying DB is dropped within 5 minutes.');
272+
} else if (r.status === 403 && r.body.error === 'paid_tier_only') {
273+
deleteBtn.disabled = false;
274+
deleteBtn.textContent = 'Delete';
275+
showUpgradeModal(r.body.message || 'Upgrade to Developer to delete resources on demand.', r.body.upgrade_url || '/pricing.html');
276+
} else {
277+
deleteBtn.disabled = false;
278+
deleteBtn.textContent = 'Delete';
279+
toast(r.body.message || 'Delete failed.', 'err');
280+
}
281+
}).catch(function () {
282+
deleteBtn.disabled = false;
283+
deleteBtn.textContent = 'Delete';
284+
toast('Network error — please retry.', 'err');
285+
});
286+
});
244287
})(cards[i]);
245288
}
289+
290+
function showUpgradeModal(msg, href) {
291+
var overlay = document.createElement('div');
292+
overlay.className = 'upgrade-overlay';
293+
overlay.innerHTML =
294+
'<div class="upgrade-modal" role="dialog" aria-modal="true">' +
295+
' <h3>Upgrade to delete</h3>' +
296+
' <p>' + esc(msg) + '</p>' +
297+
' <div class="upgrade-actions">' +
298+
' <a class="small-btn" href="' + esc(href) + '">See Developer plan</a>' +
299+
' <button type="button" class="small-btn secondary js-close">Not now</button>' +
300+
' </div>' +
301+
'</div>';
302+
document.body.appendChild(overlay);
303+
overlay.querySelector('.js-close').addEventListener('click', function () { overlay.remove(); });
304+
overlay.addEventListener('click', function (e) { if (e.target === overlay) overlay.remove(); });
305+
}
246306
function fallbackCopy(text) {
247307
try {
248308
var ta = document.createElement('textarea');

0 commit comments

Comments
 (0)