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