From 10f4b50ca51cd169c3ac4151d510aa4b57df09a7 Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 1 Jun 2026 14:46:27 +0200 Subject: [PATCH] feat(v2)(ui): per-direction fee inputs in super-config + machine modals (#38 5/5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaces the new directional fee fields in the admin dashboard so operators + the LNbits super can configure cash-in and cash-out fees independently: Templates (`templates/satmachineadmin/index.html`): - Platform fee banner now shows both directional super fractions side-by-side ("cash-in X% · cash-out Y% of each transaction's principal"). Wording updated to "principal" not "commission" since the math is now principal-based. - Super-fee edit dialog: replaces the single q-input with two (super_cash_in_fee_fraction + super_cash_out_fee_fraction); each capped at 0.15 via max attr (visual hint; server enforces). - Add-machine + edit-machine dialogs both gain operator_cash_in_fee_ fraction + operator_cash_out_fee_fraction inputs with the same 0.15 cap hint. Hint text mentions the "sits on top of platform fee, total capped at 15% per direction" semantics so operators understand the layering. JS (`static/js/index.js`): - superFeeDialog.data shape switches to the new directional fields. - openSuperFeeDialog / submitSuperFee load + POST the new shape. - _emptyMachineForm / _cleanMachineForm pass through operator directional fields (Number-coerced, default 0). - openEditMachineDialog / submitEditMachine include the operator fee fields in the form data + PUT body. - New computed `superAnyFee` drives the banner styling (sum of both directional fractions — non-zero → blue active banner; zero → muted grey "free instance" banner). All Quasar UMD components use explicit close tags per the UMD-mode parsing rule. Migration carry-over verified in dev container: pre-m009 super_fee_fraction=0.33 backfilled to super_cash_in=0.33 + super_cash_out=0.33 on migrate-up. Note this puts existing dev instances above the new 15% cap; operators will see the cap validation error on their next super-config save and must adjust to ≤0.15 per direction. Production aiolabs/server-deploy will land at 0.03 on both directions (well under cap). 164/164 tests green. Layer 1 (#38) complete; Layer 2 (#39) wire- format publisher is the next milestone. Refs: aiolabs/satmachineadmin#37 (parent), #38 (closes Layer 1). Co-Authored-By: Claude Opus 4.7 --- static/js/index.js | 41 +++++++++++++++---- templates/satmachineadmin/index.html | 59 ++++++++++++++++++++++------ 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 96b98ef..4da7ee6 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -100,7 +100,11 @@ window.app = Vue.createApp({ superFeeDialog: { show: false, saving: false, - data: {super_fee_fraction: 0, super_fee_wallet_id: ''} + data: { + super_cash_in_fee_fraction: 0, + super_cash_out_fee_fraction: 0, + super_fee_wallet_id: '' + } }, // UI configuration ----------------------------------------------- @@ -266,6 +270,17 @@ window.app = Vue.createApp({ }, computed: { + superAnyFee() { + // Banner styling key — true when either directional super fee is + // non-zero, so the banner reads as "active platform fee" instead + // of the muted grey "free instance" state. + const c = this.superConfig + if (!c) return 0 + return ( + Number(c.super_cash_in_fee_fraction || 0) + + Number(c.super_cash_out_fee_fraction || 0) + ) + }, walletOptions() { // g.user is sometimes null on initial mount in LNbits 1.4 — guard it. const wallets = this.g?.user?.wallets || [] @@ -549,7 +564,10 @@ window.app = Vue.createApp({ // ----------------------------------------------------------------- openSuperFeeDialog() { this.superFeeDialog.data = { - super_fee_fraction: this.superConfig?.super_fee_fraction ?? 0, + super_cash_in_fee_fraction: + this.superConfig?.super_cash_in_fee_fraction ?? 0, + super_cash_out_fee_fraction: + this.superConfig?.super_cash_out_fee_fraction ?? 0, super_fee_wallet_id: this.superConfig?.super_fee_wallet_id || '' } this.superFeeDialog.show = true @@ -562,7 +580,8 @@ window.app = Vue.createApp({ const {data} = await LNbits.api.request( 'PUT', SUPER_FEE_PATH, null, { - super_fee_fraction: Number(d.super_fee_fraction), + super_cash_in_fee_fraction: Number(d.super_cash_in_fee_fraction), + super_cash_out_fee_fraction: Number(d.super_cash_out_fee_fraction), super_fee_wallet_id: (d.super_fee_wallet_id || '').trim() || null } ) @@ -705,7 +724,9 @@ window.app = Vue.createApp({ location: machine.location || '', wallet_id: machine.wallet_id, fiat_code: machine.fiat_code, - is_active: machine.is_active + is_active: machine.is_active, + operator_cash_in_fee_fraction: machine.operator_cash_in_fee_fraction ?? 0, + operator_cash_out_fee_fraction: machine.operator_cash_out_fee_fraction ?? 0 } this.editMachineDialog.show = true }, @@ -723,7 +744,9 @@ window.app = Vue.createApp({ location: d.location, wallet_id: d.wallet_id, fiat_code: d.fiat_code, - is_active: d.is_active + is_active: d.is_active, + operator_cash_in_fee_fraction: Number(d.operator_cash_in_fee_fraction) || 0, + operator_cash_out_fee_fraction: Number(d.operator_cash_out_fee_fraction) || 0 } ) const idx = this.machines.findIndex(m => m.id === data.id) @@ -1475,7 +1498,9 @@ window.app = Vue.createApp({ wallet_id: null, name: '', location: '', - fiat_code: 'GTQ' + fiat_code: 'GTQ', + operator_cash_in_fee_fraction: 0, + operator_cash_out_fee_fraction: 0 } }, @@ -1485,7 +1510,9 @@ window.app = Vue.createApp({ wallet_id: d.wallet_id, name: (d.name || '').trim() || null, location: (d.location || '').trim() || null, - fiat_code: (d.fiat_code || 'GTQ').trim() + fiat_code: (d.fiat_code || 'GTQ').trim(), + operator_cash_in_fee_fraction: Number(d.operator_cash_in_fee_fraction) || 0, + operator_cash_out_fee_fraction: Number(d.operator_cash_out_fee_fraction) || 0 } }, diff --git a/templates/satmachineadmin/index.html b/templates/satmachineadmin/index.html index 8b3ddf3..ffdb730 100644 --- a/templates/satmachineadmin/index.html +++ b/templates/satmachineadmin/index.html @@ -31,17 +31,19 @@ + :class="superAnyFee > 0 ? 'bg-blue-1 text-grey-9' : 'bg-grey-2 text-grey-9'"> LNbits platform fee: - ${ (superConfig.super_fee_fraction * 100).toFixed(2) }% - of each transaction's commission. + cash-in ${ (superConfig.super_cash_in_fee_fraction * 100).toFixed(2) }% + · + cash-out ${ (superConfig.super_cash_out_fee_fraction * 100).toFixed(2) }% + of each transaction's principal. - Your remainder splits per the rules below. + Operator's per-machine fee rides on top of these.