const mapJournalEntry = obj => { return obj } window.app = Vue.createApp({ el: '#vue', mixins: [windowMixin], data() { return { balance: null, allUserBalances: [], transactions: [], accounts: [], currencies: [], users: [], settings: null, userWalletSettings: null, isAdmin: false, isSuperUser: false, castleWalletConfigured: false, userWalletConfigured: false, expenseDialog: { show: false, description: '', amount: null, expenseAccount: '', isEquity: false, reference: '', currency: null, loading: false }, payDialog: { show: false, amount: null, loading: false }, settingsDialog: { show: false, castleWalletId: '', loading: false }, userWalletDialog: { show: false, userWalletId: '', loading: false }, receivableDialog: { show: false, selectedUser: '', description: '', amount: null, revenueAccount: '', reference: '', currency: null, loading: false } } }, computed: { expenseAccounts() { return this.accounts.filter(a => a.account_type === 'expense') }, revenueAccounts() { return this.accounts.filter(a => a.account_type === 'revenue') }, amountLabel() { if (this.expenseDialog.currency) { return `Amount (${this.expenseDialog.currency}) *` } return 'Amount (sats) *' }, receivableAmountLabel() { if (this.receivableDialog.currency) { return `Amount (${this.receivableDialog.currency}) *` } return 'Amount (sats) *' }, currencyOptions() { const options = [{label: 'Satoshis (default)', value: null}] this.currencies.forEach(curr => { options.push({label: curr, value: curr}) }) return options }, userOptions() { const options = [] this.users.forEach(user => { options.push({ label: user.username, value: user.user_wallet_id }) }) return options } }, methods: { async loadBalance() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/balance', this.g.user.wallets[0].inkey ) this.balance = response.data // If super user, also load all user balances if (this.isSuperUser) { await this.loadAllUserBalances() } } catch (error) { LNbits.utils.notifyApiError(error) } }, async loadAllUserBalances() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/balances/all', this.g.user.wallets[0].adminkey ) this.allUserBalances = response.data } catch (error) { console.error('Error loading all user balances:', error) } }, async loadTransactions() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/entries/user', this.g.user.wallets[0].inkey ) this.transactions = response.data } catch (error) { LNbits.utils.notifyApiError(error) } }, async loadAccounts() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/accounts', this.g.user.wallets[0].inkey ) this.accounts = response.data } catch (error) { LNbits.utils.notifyApiError(error) } }, async loadCurrencies() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/currencies', this.g.user.wallets[0].inkey ) this.currencies = response.data } catch (error) { LNbits.utils.notifyApiError(error) } }, async loadUsers() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/users', this.g.user.wallets[0].adminkey ) this.users = response.data } catch (error) { console.error('Error loading users:', error) } }, async loadSettings() { try { // Try with admin key first to check settings const response = await LNbits.api.request( 'GET', '/castle/api/v1/settings', this.g.user.wallets[0].inkey ) this.settings = response.data this.castleWalletConfigured = !!(this.settings && this.settings.castle_wallet_id) // Check if user is super user by seeing if they can access admin features this.isSuperUser = this.g.user.super_user || false this.isAdmin = this.g.user.admin || this.isSuperUser } catch (error) { // Settings not available this.castleWalletConfigured = false } }, async loadUserWallet() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/user/wallet', this.g.user.wallets[0].inkey ) this.userWalletSettings = response.data this.userWalletConfigured = !!(this.userWalletSettings && this.userWalletSettings.user_wallet_id) } catch (error) { this.userWalletConfigured = false } }, showSettingsDialog() { this.settingsDialog.castleWalletId = this.settings?.castle_wallet_id || '' this.settingsDialog.show = true }, showUserWalletDialog() { this.userWalletDialog.userWalletId = this.userWalletSettings?.user_wallet_id || '' this.userWalletDialog.show = true }, async submitSettings() { if (!this.settingsDialog.castleWalletId) { this.$q.notify({ type: 'warning', message: 'Castle Wallet ID is required' }) return } this.settingsDialog.loading = true try { await LNbits.api.request( 'PUT', '/castle/api/v1/settings', this.g.user.wallets[0].adminkey, { castle_wallet_id: this.settingsDialog.castleWalletId } ) this.$q.notify({ type: 'positive', message: 'Settings updated successfully' }) this.settingsDialog.show = false await this.loadSettings() // Reload user wallet to reflect castle wallet for super user if (this.isSuperUser) { await this.loadUserWallet() } } catch (error) { LNbits.utils.notifyApiError(error) } finally { this.settingsDialog.loading = false } }, async submitUserWallet() { if (!this.userWalletDialog.userWalletId) { this.$q.notify({ type: 'warning', message: 'Wallet ID is required' }) return } this.userWalletDialog.loading = true try { await LNbits.api.request( 'PUT', '/castle/api/v1/user/wallet', this.g.user.wallets[0].inkey, { user_wallet_id: this.userWalletDialog.userWalletId } ) this.$q.notify({ type: 'positive', message: 'Wallet configured successfully' }) this.userWalletDialog.show = false await this.loadUserWallet() } catch (error) { LNbits.utils.notifyApiError(error) } finally { this.userWalletDialog.loading = false } }, async submitExpense() { this.expenseDialog.loading = true try { await LNbits.api.request( 'POST', '/castle/api/v1/entries/expense', this.g.user.wallets[0].inkey, { description: this.expenseDialog.description, amount: this.expenseDialog.amount, expense_account: this.expenseDialog.expenseAccount, is_equity: this.expenseDialog.isEquity, user_wallet: this.g.user.wallets[0].id, reference: this.expenseDialog.reference || null, currency: this.expenseDialog.currency || null } ) this.$q.notify({ type: 'positive', message: 'Expense added successfully' }) this.expenseDialog.show = false this.resetExpenseDialog() await this.loadBalance() await this.loadTransactions() } catch (error) { LNbits.utils.notifyApiError(error) } finally { this.expenseDialog.loading = false } }, async submitPayment() { this.payDialog.loading = true try { // First, generate an invoice for the payment const invoiceResponse = await LNbits.api.request( 'POST', '/api/v1/payments', this.g.user.wallets[0].inkey, { out: false, amount: this.payDialog.amount, memo: `Payment to Castle - ${this.payDialog.amount} sats`, unit: 'sat' } ) // Show the invoice to the user this.$q.notify({ type: 'positive', message: 'Invoice generated! Pay it to settle your balance.', timeout: 5000 }) // TODO: After payment, call /castle/api/v1/pay-balance to record it // This would typically be done via a webhook or payment verification this.payDialog.show = false this.payDialog.amount = null } catch (error) { LNbits.utils.notifyApiError(error) } finally { this.payDialog.loading = false } }, showPayBalanceDialog() { this.payDialog.amount = Math.abs(this.balance.balance) this.payDialog.show = true }, async showReceivableDialog() { // Load users if not already loaded if (this.users.length === 0 && this.isSuperUser) { await this.loadUsers() } this.receivableDialog.show = true }, async submitReceivable() { this.receivableDialog.loading = true try { await LNbits.api.request( 'POST', '/castle/api/v1/entries/receivable', this.g.user.wallets[0].adminkey, { description: this.receivableDialog.description, amount: this.receivableDialog.amount, revenue_account: this.receivableDialog.revenueAccount, user_wallet: this.receivableDialog.selectedUser, reference: this.receivableDialog.reference || null, currency: this.receivableDialog.currency || null } ) this.$q.notify({ type: 'positive', message: 'Receivable added successfully' }) this.receivableDialog.show = false this.resetReceivableDialog() await this.loadBalance() await this.loadTransactions() } catch (error) { LNbits.utils.notifyApiError(error) } finally { this.receivableDialog.loading = false } }, resetExpenseDialog() { this.expenseDialog.description = '' this.expenseDialog.amount = null this.expenseDialog.expenseAccount = '' this.expenseDialog.isEquity = false this.expenseDialog.reference = '' this.expenseDialog.currency = null }, resetReceivableDialog() { this.receivableDialog.selectedUser = '' this.receivableDialog.description = '' this.receivableDialog.amount = null this.receivableDialog.revenueAccount = '' this.receivableDialog.reference = '' this.receivableDialog.currency = null }, formatSats(amount) { return new Intl.NumberFormat().format(amount) }, formatDate(dateString) { return new Date(dateString).toLocaleDateString() }, getTotalAmount(entry) { if (!entry.lines || entry.lines.length === 0) return 0 return entry.lines.reduce((sum, line) => sum + line.debit + line.credit, 0) / 2 } }, async created() { await this.loadBalance() await this.loadTransactions() await this.loadAccounts() await this.loadCurrencies() await this.loadSettings() await this.loadUserWallet() // Load users if super user (for receivable dialog) if (this.isSuperUser) { await this.loadUsers() } } })