[feat] Payment labels (#3537)

This commit is contained in:
Vlad Stan
2025-11-21 10:33:53 +02:00
committed by GitHub
parent 3ccefb70fa
commit 7c7a04da9d
26 changed files with 930 additions and 39 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -1
View File
@@ -25,12 +25,14 @@ window.localisation.en = {
reconnect: 'Reconnect',
open_channel: 'Open Channel',
open: 'Open',
clear: 'Clear',
close_channel: 'Close Channel',
close: 'Close',
restart: 'Restart server',
image_library: 'Image Library',
save: 'Save',
save_tooltip: 'Save your changes',
must_save: 'You have unsaved changes',
credit_debit: 'Credit / Debit',
credit_hint: 'Press Enter to credit/debit wallet (negative values allowed)',
credit_label: '{denomination} to credit/debit',
@@ -694,6 +696,7 @@ window.localisation.en = {
view_list: 'View wallets as list',
view_column: 'View wallets as rows',
filter_payments: 'Filter payments',
filter_labels: 'Filter labels',
filter_date: 'Filter by date',
websocket_example: 'Websocket example',
secret_key: 'Secret Key',
@@ -709,5 +712,15 @@ window.localisation.en = {
paid: 'Paid',
funding_source_retries: 'Max Retries',
funding_source_retries_desc:
'Maximum number of retries for funding sources, before it falls back to VoidWallet.'
'Maximum number of retries for funding sources, before it falls back to VoidWallet.',
add_label: 'Add Label',
label: 'Label',
labels: 'Labels',
no_labels_defined: 'No labels defined yet',
manage_labels: 'Manage Labels',
update_label: 'Update Label',
delete_label: 'Delete Label',
add_remove_labels: 'Add or Remove Labels',
payment_labels_updated: 'Payment labels updated',
color: 'Color'
}
+2 -1
View File
@@ -94,7 +94,8 @@ window.LNbits = {
webhook: data.webhook,
webhook_status: data.webhook_status,
fiat_amount: data.fiat_amount,
fiat_currency: data.fiat_currency
fiat_currency: data.fiat_currency,
labels: data.labels
}
obj.date = moment.utc(data.created_at).local().format(window.dateFormat)
@@ -0,0 +1,36 @@
window.app.component('lnbits-label-selector', {
template: '#lnbits-label-selector',
props: ['labels'],
mixins: [window.windowMixin],
data() {
return {
labelFilter: '',
localLabels: []
}
},
methods: {
toggleLabel(label) {
const hasLabel = this.localLabels.includes(label.name)
if (hasLabel) {
const index = this.localLabels.indexOf(label.name)
if (index !== -1) {
this.localLabels.splice(index, 1)
}
} else {
this.localLabels.push(label.name)
}
},
saveLabels() {
this.$emit('update:labels', this.localLabels)
},
clearLabels() {
this.localLabels = []
this.saveLabels()
}
},
created() {
this.localLabels = [...this.labels]
}
})
@@ -121,7 +121,9 @@ window.app.component('lnbits-payment-list', {
show: false,
payment: null,
preimage: null
}
},
selectedPayment: null,
filterLabels: []
}
},
computed: {
@@ -161,12 +163,26 @@ window.app.component('lnbits-payment-list', {
this.fetchPayments()
},
searchByLabels(labels) {
if (!labels || labels.length === 0) {
this.clearLabelSeach()
return
}
this.filterLabels = labels
this.paymentsTable.filter['labels[every]'] = labels
this.fetchPayments()
},
clearDateSeach() {
this.searchDate = {from: null, to: null}
delete this.paymentFilter['time[ge]']
delete this.paymentFilter['time[le]']
this.fetchPayments()
},
clearLabelSeach() {
this.filterLabels = []
delete this.paymentsTable.filter['labels[every]']
this.fetchPayments()
},
fetchPayments(props) {
const params = LNbits.utils.prepareFilterQuery(
this.paymentsTable,
@@ -354,6 +370,47 @@ window.app.component('lnbits-payment-list', {
paymentFilter['amount[le]'] = 0
}
this.paymentFilter = paymentFilter
},
async savePaymentLabels(labels) {
if (!this.selectedPayment) {
Quasar.Notify.create({
type: 'warning',
message: 'No payment selected'
})
return
}
try {
await LNbits.api.request(
'PUT',
`/api/v1/payments/${this.selectedPayment.payment_hash}/labels`,
this.g.wallet.adminkey,
{
labels: labels
}
)
const payment = this.payments.find(
p => p.checking_id === this.selectedPayment.checking_id
)
if (payment) {
payment.labels = [...labels]
}
Quasar.Notify.create({
type: 'positive',
message: this.$t('payment_labels_updated')
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
isLightColor(color) {
try {
return Quasar.colors.luminosity(color) > 0.5
} catch (e) {
console.warning(e)
return false
}
}
},
watch: {
+118
View File
@@ -4,6 +4,7 @@ window.PageAccount = {
data() {
return {
user: null,
untouchedUser: null,
hasUsername: false,
showUserId: false,
themeOptions: [
@@ -145,6 +146,47 @@ window.PageAccount = {
nostr: {
identifier: ''
}
},
labels: [],
labelsDialog: {
show: false,
data: {
name: '',
description: '',
color: '#000000'
}
},
labelsTable: {
loading: false,
columns: [
{
name: 'actions',
align: 'left'
},
{
name: 'name',
align: 'left',
label: this.$t('Name'),
field: 'name',
sortable: true
},
{
name: 'description',
align: 'left',
label: this.$t('description'),
field: 'description'
},
{
name: 'color',
align: 'left',
label: this.$t('color'),
field: 'color'
}
],
pagination: {
rowsPerPage: 6,
page: 1
}
}
}
},
@@ -159,6 +201,11 @@ window.PageAccount = {
}
}
},
computed: {
isUserTouched() {
return JSON.stringify(this.user) !== JSON.stringify(this.untouchedUser)
}
},
methods: {
activeLanguage(lang) {
return window.i18n.global.locale === lang
@@ -181,6 +228,7 @@ window.PageAccount = {
}
)
this.user = data
this.untouchedUser = JSON.parse(JSON.stringify(data))
this.hasUsername = !!data.username
Quasar.Notify.create({
type: 'positive',
@@ -220,6 +268,7 @@ window.PageAccount = {
}
)
this.user = data
this.untouchedUser = JSON.parse(JSON.stringify(data))
this.hasUsername = !!data.username
this.credentialsData.show = false
Quasar.Notify.create({
@@ -242,6 +291,7 @@ window.PageAccount = {
}
)
this.user = data
this.untouchedUser = JSON.parse(JSON.stringify(data))
this.hasUsername = !!data.username
this.credentialsData.show = false
this.$q.notify({
@@ -558,6 +608,73 @@ window.PageAccount = {
copyAssetLinkToClipboard(asset) {
const assetUrl = `${window.location.origin}/api/v1/assets/${asset.id}/binary`
this.copyText(assetUrl)
},
addUserLabel() {
if (!this.labelsDialog.data.name) {
this.$q.notify({
type: 'warning',
message: 'Name is required.'
})
return
}
if (!this.labelsDialog.data.color) {
this.$q.notify({
type: 'warning',
message: 'Color is required.'
})
return
}
this.user.extra.labels = this.user.extra.labels || []
const duplicate = this.user.extra.labels.find(
label => label.name === this.labelsDialog.data.name
)
if (duplicate) {
this.$q.notify({
type: 'warning',
message: 'A label with this name already exists.'
})
return
}
this.user.extra.labels.unshift({...this.labelsDialog.data})
this.labelsDialog.show = false
return true
},
openAddLabelDialog() {
this.labelsDialog.data = {
name: '',
description: '',
color: '#000000'
}
this.labelsDialog.show = true
},
openEditLabelDialog(label) {
this.labelsDialog.data = {
name: label.name,
description: label.description,
color: label.color
}
this.labelsDialog.show = true
},
updateUserLabel() {
const label = this.labelsDialog.data
const existingLabels = JSON.parse(JSON.stringify(this.user.extra.labels))
this.user.extra.labels = this.user.extra.labels.filter(
l => l.name !== label.name
)
const labelUpdated = this.addUserLabel()
if (!labelUpdated) {
this.user.extra.labels = existingLabels
}
this.labelsDialog.show = false
},
deleteUserLabel(label) {
LNbits.utils
.confirmDialog('Are you sure you want to delete this label?')
.onOk(() => {
this.user.extra.labels = this.user.extra.labels.filter(
l => l.name !== label.name
)
})
}
},
@@ -565,6 +682,7 @@ window.PageAccount = {
try {
const {data} = await LNbits.api.getAuthenticatedUser()
this.user = data
this.untouchedUser = JSON.parse(JSON.stringify(data))
this.hasUsername = !!data.username
if (!this.user.extra) this.user.extra = {}
} catch (e) {
+1
View File
@@ -84,6 +84,7 @@
"js/components/lnbits-manage-wallet-list.js",
"js/components/lnbits-language-dropdown.js",
"js/components/lnbits-payment-list.js",
"js/components/lnbits-label-selector.js",
"js/components/extension-settings.js",
"js/components/data-fields.js",
"js/components.js",