feat(ui): wire admin add-account endpoint into Chart of Accounts

Surface the existing POST /api/v1/admin/accounts endpoint in the UI: a
super-user-only 'Add Account' button on the Chart of Accounts card opens
a dialog for the hierarchical account name + optional description, posts
with the wallet admin key (require_super_user), then reloads accounts.
Client-side prefix validation mirrors the server's _VALID_ACCOUNT_PREFIXES.
No currency input — an Open directive does not require currency constraints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-15 20:31:42 +02:00
commit 9dd46e818c
2 changed files with 103 additions and 1 deletions

View file

@ -69,6 +69,12 @@ window.app = Vue.createApp({
userWalletId: '',
loading: false
},
addAccountDialog: {
show: false,
name: '',
description: '',
loading: false
},
receivableDialog: {
show: false,
selectedUser: '',
@ -566,6 +572,45 @@ window.app = Vue.createApp({
this.syncingAccounts = false
}
},
showAddAccountDialog() {
this.addAccountDialog.name = ''
this.addAccountDialog.description = ''
this.addAccountDialog.show = true
},
async submitAddAccount() {
const name = (this.addAccountDialog.name || '').trim()
const validPrefixes = ['Assets:', 'Liabilities:', 'Equity:', 'Income:', 'Expenses:']
if (!validPrefixes.some(p => name.startsWith(p))) {
this.$q.notify({
type: 'warning',
message: `Account name must start with one of: ${validPrefixes.join(', ')}`
})
return
}
this.addAccountDialog.loading = true
try {
const {data} = await LNbits.api.request(
'POST',
'/libra/api/v1/admin/accounts',
this.g.user.wallets[0].adminkey,
{
name,
description: this.addAccountDialog.description || null
}
)
this.$q.notify({
type: 'positive',
message: `Account ${data.account_name} created` +
(data.synced_to_libra_db ? '' : ' (sync pending)')
})
this.addAccountDialog.show = false
await this.loadAccounts()
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.addAccountDialog.loading = false
}
},
showSettingsDialog() {
this.settingsDialog.libraWalletId = this.settings?.libra_wallet_id || ''
this.settingsDialog.favaUrl = this.settings?.fava_url || 'http://localhost:3333'

View file

@ -857,7 +857,20 @@
<!-- Chart of Accounts -->
<q-card>
<q-card-section>
<h6 class="q-my-none q-mb-md">Chart of Accounts</h6>
<div class="row items-center q-mb-md">
<h6 class="q-my-none">Chart of Accounts</h6>
<q-space></q-space>
<q-btn
v-if="isSuperUser"
unelevated
dense
size="sm"
color="primary"
icon="add"
label="Add Account"
@click="showAddAccountDialog"
></q-btn>
</div>
<q-list dense v-if="accounts.length > 0">
<q-item v-for="account in accounts" :key="account.id">
<q-item-section>
@ -1232,6 +1245,50 @@
</q-card>
</q-dialog>
<!-- Add Account Dialog -->
<q-dialog v-model="addAccountDialog.show" position="top">
<q-card v-if="addAccountDialog.show" class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="submitAddAccount" class="q-gutter-md">
<div class="text-h6 q-mb-md">Add Account</div>
<q-input
filled
dense
v-model.trim="addAccountDialog.name"
label="Account Name *"
placeholder="e.g., Expenses:Services:Domain"
hint="Full hierarchical name. Must start with Assets:, Liabilities:, Equity:, Income: or Expenses:"
></q-input>
<q-input
filled
dense
v-model.trim="addAccountDialog.description"
label="Description"
placeholder="Optional notes about this account"
></q-input>
<div class="text-caption text-grey">
Creates an Open directive in the Beancount ledger and syncs it into Libra
so permissions can be granted. Per-user accounts are managed automatically.
</div>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
type="submit"
:loading="addAccountDialog.loading"
:disable="!addAccountDialog.name"
>
Create Account
</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</q-form>
</q-card>
</q-dialog>
<!-- Receivable Dialog -->
<q-dialog v-model="receivableDialog.show" position="top">
<q-card v-if="receivableDialog.show" class="q-pa-lg q-pt-xl lnbits__dialog-card">