From eefabc34411d9e5071b2d3fbb2d28404bc87719e Mon Sep 17 00:00:00 2001 From: padreug Date: Sat, 8 Nov 2025 10:14:24 +0100 Subject: [PATCH] Enables equity eligibility for users Allows superusers to grant and revoke equity eligibility for users. Adds UI components for managing equity eligibility. Equity-eligible users can then contribute expenses as equity. --- static/js/index.js | 15 +++ static/js/permissions.js | 131 +++++++++++++++++++++- templates/castle/index.html | 18 +++ templates/castle/permissions.html | 175 ++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+), 1 deletion(-) diff --git a/static/js/index.js b/static/js/index.js index 2517657..4daad64 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -15,6 +15,7 @@ window.app = Vue.createApp({ users: [], settings: null, userWalletSettings: null, + userInfo: null, // User information including equity eligibility isAdmin: false, isSuperUser: false, castleWalletConfigured: false, @@ -353,6 +354,19 @@ window.app = Vue.createApp({ console.error('Error loading users:', error) } }, + async loadUserInfo() { + try { + const response = await LNbits.api.request( + 'GET', + '/castle/api/v1/user/info', + this.g.user.wallets[0].inkey + ) + this.userInfo = response.data + } catch (error) { + console.error('Error loading user info:', error) + this.userInfo = { is_equity_eligible: false } + } + }, async loadSettings() { try { // Try with admin key first to check settings @@ -1457,6 +1471,7 @@ window.app = Vue.createApp({ // Load settings first to determine if user is super user await this.loadSettings() await this.loadUserWallet() + await this.loadUserInfo() await this.loadExchangeRate() await this.loadBalance() await this.loadTransactions() diff --git a/static/js/permissions.js b/static/js/permissions.js index f74aa2b..4575da1 100644 --- a/static/js/permissions.js +++ b/static/js/permissions.js @@ -7,13 +7,19 @@ window.app = Vue.createApp({ accounts: [], users: [], filteredUsers: [], + equityEligibleUsers: [], loading: false, granting: false, revoking: false, + grantingEquity: false, + revokingEquity: false, activeTab: 'by-user', showGrantDialog: false, showRevokeDialog: false, + showGrantEquityDialog: false, + showRevokeEquityDialog: false, permissionToRevoke: null, + equityToRevoke: null, isSuperUser: false, grantForm: { user_id: '', @@ -22,6 +28,10 @@ window.app = Vue.createApp({ notes: '', expires_at: '' }, + grantEquityForm: { + user_id: '', + notes: '' + }, permissionTypeOptions: [ { value: 'read', @@ -326,6 +336,124 @@ window.app = Vue.createApp({ if (!dateString) return '-' const date = new Date(dateString) return date.toLocaleString() + }, + + async loadEquityEligibleUsers() { + if (!this.isSuperUser) { + return + } + + try { + const response = await LNbits.api.request( + 'GET', + '/castle/api/v1/admin/equity-eligibility', + this.g.user.wallets[0].adminkey + ) + this.equityEligibleUsers = response.data || [] + } catch (error) { + console.error('Failed to load equity-eligible users:', error) + this.$q.notify({ + type: 'negative', + message: 'Failed to load equity-eligible users', + caption: error.message || 'Unknown error', + timeout: 5000 + }) + } + }, + + async grantEquityEligibility() { + if (!this.grantEquityForm.user_id) { + this.$q.notify({ + type: 'warning', + message: 'Please select a user', + timeout: 3000 + }) + return + } + + this.grantingEquity = true + try { + const payload = { + user_id: this.grantEquityForm.user_id, + is_equity_eligible: true + } + + if (this.grantEquityForm.notes) { + payload.notes = this.grantEquityForm.notes + } + + await LNbits.api.request( + 'POST', + '/castle/api/v1/admin/equity-eligibility', + this.g.user.wallets[0].adminkey, + payload + ) + + this.$q.notify({ + type: 'positive', + message: 'Equity eligibility granted successfully', + timeout: 3000 + }) + + this.showGrantEquityDialog = false + this.resetGrantEquityForm() + await this.loadEquityEligibleUsers() + } catch (error) { + console.error('Failed to grant equity eligibility:', error) + this.$q.notify({ + type: 'negative', + message: 'Failed to grant equity eligibility', + caption: error.message || 'Unknown error', + timeout: 5000 + }) + } finally { + this.grantingEquity = false + } + }, + + confirmRevokeEquity(equity) { + this.equityToRevoke = equity + this.showRevokeEquityDialog = true + }, + + async revokeEquityEligibility() { + if (!this.equityToRevoke) return + + this.revokingEquity = true + try { + await LNbits.api.request( + 'DELETE', + `/castle/api/v1/admin/equity-eligibility/${this.equityToRevoke.user_id}`, + this.g.user.wallets[0].adminkey + ) + + this.$q.notify({ + type: 'positive', + message: 'Equity eligibility revoked successfully', + timeout: 3000 + }) + + this.showRevokeEquityDialog = false + this.equityToRevoke = null + await this.loadEquityEligibleUsers() + } catch (error) { + console.error('Failed to revoke equity eligibility:', error) + this.$q.notify({ + type: 'negative', + message: 'Failed to revoke equity eligibility', + caption: error.message || 'Unknown error', + timeout: 5000 + }) + } finally { + this.revokingEquity = false + } + }, + + resetGrantEquityForm() { + this.grantEquityForm = { + user_id: '', + notes: '' + } } }, @@ -338,7 +466,8 @@ window.app = Vue.createApp({ if (this.isSuperUser) { await Promise.all([ this.loadPermissions(), - this.loadUsers() + this.loadUsers(), + this.loadEquityEligibleUsers() ]) } } diff --git a/templates/castle/index.html b/templates/castle/index.html index c907303..86613e7 100644 --- a/templates/castle/index.html +++ b/templates/castle/index.html @@ -723,6 +723,7 @@ > + +
+ + + +
+ + @@ -170,6 +171,68 @@ + + + +
+ +
+ +
+
+ + Admin access required + +
+
+ +
+ +

No equity-eligible users yet

+
+ +
+ + +
+
+
+ + {% raw %}{{ equity.user_id }}{% endraw %} +
+
+ Equity Account: {% raw %}{{ equity.equity_account_name }}{% endraw %} +
+
+ Notes: {% raw %}{{ equity.notes }}{% endraw %} +
+
+ Granted: {% raw %}{{ formatDate(equity.granted_at) }}{% endraw %} +
+
+
+ + Revoke Equity Eligibility + +
+
+
+
+
+
@@ -348,4 +411,116 @@ + + + + + +
Grant Equity Eligibility
+
+ Grant a user the ability to contribute expenses as equity instead of liability. + An equity account will be automatically created for this user. +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + +
Revoke Equity Eligibility?
+
+ + +

Are you sure you want to revoke equity eligibility for this user?

+

+ Note: This will prevent them from adding new expenses as equity contributions. + Their existing equity account and contributions will remain unchanged. +

+ + + + User + {% raw %}{{ equityToRevoke.user_id }}{% endraw %} + + + + + Equity Account + {% raw %}{{ equityToRevoke.equity_account_name }}{% endraw %} + + + +
+ + + + + +
+
{% endblock %}