@@ -194,13 +194,26 @@ const CP = (() => {
194194 async function loadDashboardStats ( ) {
195195 try {
196196 const data = await api ( '/api/earnings/summary' ) ;
197- setTextContent ( 'total-earnings' , formatCurrency ( data . total || 0 ) ) ;
197+ const totalBonus = data . total_bonus || 0 ;
198+ const displayTotal = totalBonus > 0 ? ( data . total_adjusted || 0 ) : ( data . total || 0 ) ;
199+ setTextContent ( 'total-earnings' , formatCurrency ( displayTotal ) ) ;
198200 setTextContent ( 'today-earnings' , formatCurrency ( data . today || 0 ) ) ;
199201 setTextContent ( 'month-earnings' , formatCurrency ( data . month || 0 ) ) ;
200202 setTextContent ( 'active-services' , data . active_services || 0 ) ;
201203
204+ // Show promo offset footnote under total
205+ const bonusNote = document . getElementById ( 'total-bonus-note' ) ;
206+ if ( bonusNote ) {
207+ if ( totalBonus > 0 ) {
208+ bonusNote . textContent = `\u2212${ formatCurrency ( totalBonus ) } promo` ;
209+ bonusNote . style . display = '' ;
210+ } else {
211+ bonusNote . style . display = 'none' ;
212+ }
213+ }
214+
202215 // Update topbar
203- setTextContent ( 'topbar-total' , formatCurrency ( data . total || 0 ) ) ;
216+ setTextContent ( 'topbar-total' , formatCurrency ( displayTotal ) ) ;
204217
205218 // Change indicators
206219 if ( data . today_change !== undefined ) {
@@ -242,8 +255,8 @@ const CP = (() => {
242255 case 'balance' : {
243256 const ba = breakdownMap [ a . slug ] ;
244257 const bb = breakdownMap [ b . slug ] ;
245- va = ( ba && ba . balance ) || a . balance || 0 ;
246- vb = ( bb && bb . balance ) || b . balance || 0 ;
258+ va = ( ba && ba . signup_bonus ) ? ( ba . balance_adjusted ?? ba . balance ) : ( ( ba && ba . balance ) || a . balance || 0 ) ;
259+ vb = ( bb && bb . signup_bonus ) ? ( bb . balance_adjusted ?? bb . balance ) : ( ( bb && bb . balance ) || b . balance || 0 ) ;
247260 break ;
248261 }
249262 case 'change' : {
@@ -411,20 +424,26 @@ const CP = (() => {
411424
412425 // Balance + delta from breakdown
413426 const balance = ( bk && bk . balance ) || svc . balance || 0 ;
427+ const signupBonus = ( bk && bk . signup_bonus ) || 0 ;
428+ const balanceAdj = signupBonus > 0 ? ( ( bk && bk . balance_adjusted ) ?? Math . max ( 0 , balance - signupBonus ) ) : balance ;
414429 const currency = ( bk && bk . currency ) || svc . currency || 'USD' ;
415430 const delta = bk ? bk . delta : 0 ;
416431 const deltaSign = delta > 0 ? '+' : '' ;
417432 const deltaClass = delta > 0 ? 'positive' : delta < 0 ? 'negative' : '' ;
418433 const deltaStr = delta !== 0 ? `${ deltaSign } ${ formatCurrency ( delta , currency ) } ` : '--' ;
419- const nativeLabel = formatNative ( balance , currency ) ;
434+ const displayBalance = signupBonus > 0 ? balanceAdj : balance ;
435+ const nativeLabel = formatNative ( displayBalance , currency ) ;
436+ const bonusLabel = signupBonus > 0
437+ ? `<div style="font-size:0.6rem; color:var(--text-muted);">\u2212${ formatCurrency ( signupBonus , currency ) } promo</div>`
438+ : '' ;
420439 const disconnectedLabel = svc . collector_disconnected
421440 ? `<div style="font-size:0.6rem; color:#ef4444; font-weight:500; display:flex; align-items:center; justify-content:flex-end; gap:4px;">disconnected${ _isOwner ? ` <button class="btn btn-ghost" onclick="event.stopPropagation(); CP.openCredentialModal('${ escapeHtml ( svc . slug ) } ')" style="font-size:0.6rem; padding:1px 5px; line-height:1.2; color:#ef4444; border:1px solid #ef4444; border-radius:3px; cursor:pointer;">update</button>` : '' } </div>`
422441 : '' ;
423442 let balanceHtml ;
424443 if ( nativeLabel ) {
425- balanceHtml = `${ formatCurrency ( balance , currency ) } <div style="font-size:0.65rem;color:var(--text-muted);">${ nativeLabel } </div>${ disconnectedLabel } ` ;
444+ balanceHtml = `${ formatCurrency ( displayBalance , currency ) } <div style="font-size:0.65rem;color:var(--text-muted);">${ nativeLabel } </div>${ bonusLabel } ${ disconnectedLabel } ` ;
426445 } else {
427- balanceHtml = `${ formatCurrency ( balance , currency ) } ${ disconnectedLabel } ` ;
446+ balanceHtml = `${ formatCurrency ( displayBalance , currency ) } ${ bonusLabel } ${ disconnectedLabel } ` ;
428447 }
429448
430449 // CPU/Memory — skip for external; show avg for multi-instance
@@ -469,16 +488,22 @@ const CP = (() => {
469488 ? ` <span class="badge badge-instances" title="${ instances } instance${ instances > 1 ? 's' : '' } ">${ instances } x</span>`
470489 : '' ;
471490
491+ // Settings gear (owner-only) — opens credential + bonus modal
492+ const settingsBtn = _isOwner
493+ ? `<button class="btn btn-icon" onclick="event.stopPropagation(); CP.openCredentialModal('${ escapeHtml ( svc . slug ) } ')" title="Credentials & settings">
494+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
495+ </button>` : '' ;
496+
472497 // For multi-instance: expand chevron, no container action buttons in main row
473498 // For single instance: show action buttons directly
474499 let actionBtns ;
475500 if ( isMulti ) {
476501 const chevron = `<button class="btn btn-icon expand-toggle" onclick="event.stopPropagation(); CP.toggleInstances('${ svc . slug } ')" title="Expand instances">
477502 <svg class="expand-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
478503 </button>` ;
479- actionBtns = `<div class="action-btns">${ claimBtn } ${ chevron } </div>` ;
504+ actionBtns = `<div class="action-btns">${ claimBtn } ${ settingsBtn } ${ chevron } </div>` ;
480505 } else if ( isExternal ) {
481- actionBtns = `<div class="action-btns">${ claimBtn } </div>` ;
506+ actionBtns = `<div class="action-btns">${ claimBtn } ${ settingsBtn } </div>` ;
482507 } else {
483508 // Single instance — build container buttons targeting the right node
484509 const inst = details [ 0 ] || { } ;
@@ -487,6 +512,7 @@ const CP = (() => {
487512 const disabledAttr = noDocker ? ' disabled title="No Docker access"' : '' ;
488513 actionBtns = `<div class="action-btns">
489514 ${ claimBtn }
515+ ${ settingsBtn }
490516 ${ _canWrite ? `
491517 <button class="btn btn-icon" onclick="CP.restartService('${ svc . slug } ${ wParam } )" title="Restart"${ disabledAttr } >
492518 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
@@ -716,9 +742,28 @@ const CP = (() => {
716742 } ) . join ( '' ) ;
717743 const hint = col . hint || '' ;
718744
745+ // Signup bonus offset field — show current value from config
746+ const bonusKey = `${ slug } _signup_bonus` ;
747+ const currentBonus = config [ bonusKey ] || '' ;
748+ const payCurrency = col . currency || 'USD' ;
749+ const currencyLabel = payCurrency === 'USD' ? '$' : payCurrency ;
750+ const bonusHtml = `
751+ <div style="margin-top:14px; padding-top:12px; border-top:1px solid var(--border);">
752+ <label style="display:block; font-size:0.8rem; color:var(--text-secondary); margin-bottom:4px;">Signup Bonus Offset (${ escapeHtml ( currencyLabel ) } )</label>
753+ <div style="display:flex; align-items:center; gap:6px;">
754+ <input class="form-input cred-modal-input" type="number" step="0.01" min="0"
755+ data-config="${ escapeHtml ( bonusKey ) } "
756+ value="${ escapeHtml ( currentBonus ) } "
757+ placeholder="0.00"
758+ style="width:100px;">
759+ <span style="font-size:0.75rem; color:var(--text-muted);">Subtract promotional credits from displayed balance</span>
760+ </div>
761+ </div>` ;
762+
719763 body . innerHTML = `
720764 ${ hint ? `<p style="font-size:0.75rem; color:var(--text-muted); margin:0 0 12px; line-height:1.4;">${ hint } </p>` : '' }
721765 ${ fieldsHtml }
766+ ${ bonusHtml }
722767 <div style="display:flex; gap:8px; margin-top:14px;">
723768 <button class="btn btn-primary btn-sm" onclick="CP.saveCredentialModal()">Save</button>
724769 <button class="btn btn-ghost btn-sm" onclick="CP.closeModal('cred-modal')">Cancel</button>
0 commit comments